solr 配置IKAnalyzer中文分词

配置IKAnalyzer的中文分词,基于solr6.5.0

1、首先下载IKAnalyzer,这是最新的支持solr 6.5,
http://files.cnblogs.com/files/wander1129/ikanalyzer-solr5.zip

这里也有提供

解压后会有四个文件:
ext.dic 为扩展字典
stopword.dic 为停止词字典
IKAnalyzer.cfg.xml 为配置文件
ik-analyzer-solr5-5.x.jar 为分词jar包

2、将文件夹下的IKAnalyzer.cfg.xml, ext.dic和stopword.dic 三个文件复制到/home/hewentian/ProjectD/solr-6.5.0/server/resources目录下(视个人安装solr,而适当修改)
注意: 记得将stopword.dic,ext.dic的编码方式为UTF-8 无BOM的编码方式。

并修改IKAnalyzer.cfg.xml(一般默认即可)

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>

<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic;</entry>

<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic;</entry>
</properties>

3、在ext.dic 里增加自己的扩展词典,例如,唯品会 聚美优品

4、复制ik-analyzer-solr5-5.x.jar/home/hewentian/ProjectD/solr-6.5.0/server/solr-webapp/webapp/WEB-INF/lib目录下

5、在/home/hewentian/ProjectD/solr-6.5.0/server/solr/mysqlCore/conf/managed-schema文件的前增加如下配置

1
2
3
4
5
<!-- 我添加的IK分词 -->
<fieldType name="text_ik" class="solr.TextField">
<analyzer type="index" isMaxWordLength="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
<analyzer type="query" isMaxWordLength="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

重启solr:

1
2
3
$ cd /home/hewentian/ProjectD/solr-6.5.0/bin
$ ./solr stop -all
$ ./solr start

在浏览器中打开:
http://localhost:8983/solr/#/mysqlCore/analysis

在Field Value (Index)中输入:中华人民共和国
在Analyse Fieldname / FieldType:选择 text_ik

点[Analyse Values]即可看到分词

至此,配置完成。

下面说说配置solr默认的中文分词:

1.首先将需要的lucene-analyzers-smartcn-6.5.0.jar复制到WEB-INF/lib/目录下

1
2
$ cd /home/hewentian/ProjectD/solr-6.5.0
$ cp contrib/analysis-extras/lucene-libs/lucene-analyzers-smartcn-6.5.0.jar server/solr-webapp/webapp/WEB-INF/lib/

2、为mysqlCore添加对中文分词的支持,在/home/hewentian/ProjectD/solr-6.5.0/server/solr/mysqlCore/conf/managed-schema文件的前增加如下配置

1
2
3
4
5
6
7
8
<fieldType name="text_smartcn" class="solr.TextField" positionIncrementGap="0">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
</analyzer>
</fieldType>

重启solr测试即可。可以与IK分词的结果作对比。

solr 配置拼音检索

配置拼音检索,基于solr6.5.0

1、前期准备,需要用到pinyin4j-2.5.0.jar、pinyinAnalyzer4.3.1.jar这两个jar包,下载地址:
http://files.cnblogs.com/files/wander1129/pinyin.zip

这里也有提供pinyin.zip

解压后会有2个文件:
pinyin4j-2.5.0.jar
pinyinAnalyzer4.3.1.jar

2、将上面的两个文件复制到/home/hewentian/ProjectD/solr-6.5.0/server/solr-webapp/webapp/WEB-INF/lib目录下,还有执行如下命令,

1
2
$ cd /home/hewentian/ProjectD/solr-6.5.0
$ cp contrib/analysis-extras/lucene-libs/lucene-analyzers-smartcn-6.5.0.jar server/solr-webapp/webapp/WEB-INF/lib/

将需要的lucene-analyzers-smartcn-6.5.0.jar复制到WEB-INF/lib/目录下

3、在/home/hewentian/ProjectD/solr-6.5.0/server/solr/mysqlCore/conf/managed-schema文件的前增加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
<fieldType name="text_pinyin" class="solr.TextField" positionIncrementGap="0">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory" />
<filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
<filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="1" maxGram="20" />
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory" />
<filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
<filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="1" maxGram="20" />
</analyzer>
</fieldType>

重启solr:

1
2
3
$ cd /home/hewentian/ProjectD/solr-6.5.0/bin
$ ./solr stop -all
$ ./solr start

4、在浏览器中打开:
http://localhost:8983/solr/#/mysqlCore/analysis

在Field Value (Index)中输入:中国
在Analyse Fieldname / FieldType:选择 text_pinyin

点[Analyse Values]即可看到分词

至此,配置完成。

安装 JDK

下面分别说说在WindowsLinux平台下面安装JDK

1.在Windows下面安装JDK

JDK环境变量配置的步骤如下:
1. 我的电脑 -> 属性 -> 高级 -> 环境变量;
2. 配置用户变量:
    a.新建 JAVA_HOME
    C:\Program Files\Java\jdk1.8.0_101 (JDK的安装路径,务必替换成你自已的)
    b.新建 PATH(如果系统本身已有,则修改,加入下面的代码)
    %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
    c.新建 CLASSPATH
    .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
3. 测试环境变量配置是否成功:
    开始 -> 运行 -> cmd
    键盘敲入: java
出现相应的命令,而不是出错信息,即表示配置成功!

环境变量配置的理解:

  1. PATH:作用是指定命令搜索路径,在命令行下面执行命令如:javac编译java程序时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序。我们需要把jdk安装目录下的bin目录增加到现有的PATH变量中,bin目录中包含经常要用到的可执行文件如javac/java/javadoc等待,设置好PATH变量后,就可以在任何目录下执行javac/java等工具了;
  2. CLASSPATH:作用是指定类搜索路径,要使用已经编写好的类,前提当然是能够找到它们了,JVM就是通过CLASSPTH来寻找类的。我们需要把jdk安装目录下的lib子目录中的dt.jartools.jar设置到CLASSPATH中,当然,当前目录“.”也必须加入到该变量中;
  3. JAVA_HOME:它指向jdk的安装目录,Eclipse/NetBeans/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk

2.在Linux下面安装JDK

第一步:下载jdk-8u102-linux-x64.tar.gz
直接在ORACLE的官网中下载就可以:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
PS:要注意系统版本的选择,32位 还是 64位,执行如下命令即可知道答案

1
2
$ uname -a
Linux hewentian-Lenovo-IdeaPad-Y470 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

第二步:解压安装
接着就是解压tar.gz的文件了

1
2
$ cd /home/hewentian/Downloads
$ tar -xzvf jdk-8u102-linux-x64.tar.gz

解压后会得到jdk1.8.0_102文件夹,接着就是将解压出来的文件夹移动到/usr/local的目录下
在这之前当然需要你拥有root的权限su root(对于ubuntu)再输入root账户的密码,做好这些准备之后,我们就可以把jdk的文件移动到我们想要的位置了。

1
2
3
$ su root
Password:
$ mv jdk1.8.0_102 /usr/local

第三步:修改环境变量,若PATH已存在,则用冒号作间隔,将jdk的bin目录地址加上,这样java的环境变量将配置成功了,配置完成如下:

1
2
3
4
5
6
vi /etc/profile 

export JAVA_HOME=/usr/local/jdk1.8.0_102
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$JAVA_HOME:$PATH

记得保存 后执行

1
$ source /etc/profile

第四步:但这样默认使用的JDK可能还不是我们刚才安装的,因为ubuntu可能还会有默认的jdk,如openjdk;所以,为了使默认使用的是我们安装的jdk,还需执行如下命令:

1
2
3
4
5
6
7
$ sudo update-alternatives --install /usr/bin/java java /usr/local/jdk1.8.0_102/bin/java 300  
$ sudo update-alternatives --install /usr/bin/javac javac /usr/local/jdk1.8.0_102/bin/javac 300

$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
这时如果有多个jdk的话(比如openJDK和SUN JDK),就会出来一个列表,当前默认的会在列表前面有一个"`*`"号,
这时我们就要选择我们刚装的SUN JDK的java的那个序号,输入这个序号,回车就行了。

第五步:成功执行命令后,我们安装的JDK就是系统默认的了,执行如下命令,就可以成功看到JDK的相关信息了如:

1
2
3
4
$ java -version 
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

第六步:在ubuntu下面,可能root用户无法使用JDK,这样的话,执行下面的配置:

1
2
3
4
5
6
7
8
9
10
$ vi /root/.bashrc

在文件中加上(注意,下面的变量参数有${}的,与上面的不同)
export JAVA_HOME=/usr/local/jdk1.8.0_102
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH
export PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin:$PATH

最后:
source /root/.bashrc

mysql 学习笔记

mysql 查询重复数据

1
2
3
4
SELECT user_name, COUNT(*) AS c 
FROM user_table
GROUP BY user_name
HAVING c > 1;

当查询几个列的组合的时候,可以这样

1
2
3
4
SELECT CONCAT(first_name, '_', last_name) AS user_name, COUNT(*) AS c 
FROM user_table
GROUP BY user_name
HAVING c > 1;

当使用CONCAT(str1,str2,...)函数的时候,只要有一个参数为NULL,则整个结果为NULL,而有时候这不是我们所想要的结果,这时候,我们可以使用IFNULL(expr1,expr2)函数来做一个转换,当expr1NULL的时候,表达式的结果为expr2,否则为expr1,示例如下:

1
2
3
SELECT CONCAT('Tim', ' ', 'Ho') FROM DUAL; # Tim Ho
SELECT IFNULL(NULL, 'expr2') FROM DUAL; # expr2
SELECT IFNULL('expr1', 'expr2') FROM DUAL; # expr1

索引

显示索引信息:

1
SHOW INDEX FROM tableName;

创建索引,语法一:

1
2
3
ALTER TABLE tableName ADD INDEX indexName(columnName);

eg: ALTER TABLE t_user ADD INDEX index_name(name);

创建索引,语法二:

1
2
3
CREATE INDEX indexName ON tableName(columnName(length));

eg: CREATE INDEX index_sex ON t_user(sex);

如果是CHAR,VARCHAR类型,length可以小于字段实际长度或者省略;如果是BLOB和TEXT类型,必须指定length。

创建索引,语法三:
创建表的时候直接指定

1
2
3
4
5
CREATE TABLE tableName (
ID INT NOT NULL,
columnName VARCHAR(16) NOT NULL,
INDEX [indexName] (columnName(length))
);

删除索引:

1
DROP INDEX [indexName] ON tableName;

唯一索引

创建索引,语法一:

1
2
3
ALTER TABLE tableName ADD UNIQUE [indexName] (columnName)

eg: ALTER TABLE t_user ADD UNIQUE index_name (name);

创建索引,语法二:

1
2
3
CREATE UNIQUE INDEX indexName ON tableName(columnName(length))

eg: CREATE UNIQUE INDEX index_name ON t_user(name);

t_user结构如下:

1
2
3
4
5
6
7
8
9
10
11
Field        Type          Collation        Null    Key     Default            Extra           Privileges                       Comment
----------- ------------ --------------- ------ ------ ----------------- -------------- ------------------------------- ----------------------
id int(11) (NULL) NO PRI (NULL) auto_increment select,insert,update,references 用户ID
login_name varchar(100) utf8_general_ci NO (NULL) select,insert,update,references 登录名
passwd varchar(100) utf8_general_ci NO (NULL) select,insert,update,references 登录密码
nick_name varchar(100) utf8_general_ci YES (NULL) select,insert,update,references 别名
gender tinyint(4) (NULL) YES 1 select,insert,update,references 性别:1.男;2.
phone varchar(20) utf8_general_ci YES (NULL) select,insert,update,references 手机号码
address varchar(255) utf8_general_ci YES (NULL) select,insert,update,references 地址
create_time timestamp (NULL) YES CURRENT_TIMESTAMP select,insert,update,references 创建时间
update_time timestamp (NULL) YES (NULL) select,insert,update,references 修改时间

给 login_name 和 phone 添加联合唯一约束

1
ALTER TABLE t_user ADD UNIQUE KEY (login_name, phone);

你也可以在创建唯一约束的时候,给唯一约束起名

1
ALTER TABLE t_user ADD UNIQUE KEY `uk_login_name_phone` (login_name, phone);

查看结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SHOW CREATE TABLE t_user;

CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`login_name` varchar(100) NOT NULL COMMENT '登录名',
`passwd` varchar(100) NOT NULL COMMENT '登录密码',
`nick_name` varchar(100) DEFAULT NULL COMMENT '别名',
`gender` tinyint(4) DEFAULT '1' COMMENT '性别:1.男;2.女',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号码',
`address` varchar(255) DEFAULT NULL COMMENT '地址',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_login_name_phone` (`login_name`,`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

删除唯一约束:

1
ALTER TABLE t_user DROP INDEX uk_login_name_phone;

LIMIT 语句

取得某一范围的记录集。如前5条、第5到第10条,数据分页等。LIMIT写在查询语句的最后位置上。

语法:
SELECT * FROM tableName LIMIT 长度;
SELECT * FROM tableName LIMIT 起始位置, 长度;
1
2
SELECT * FROM t_user WHERE id > 2 LIMIT 5;     // 前5条记录
SELECT * FROM t_user WHERE id > 2 LIMIT 5, 10; // 第5条记录之后的前10条记录,不包括第5条记录

MySQL中GROUP_CONCAT函数

https://www.mysqltutorial.org/mysql-group_concat/

完整的语法如下:

1
2
3
4
5
GROUP_CONCAT(
DISTINCT expression
ORDER BY expression
SEPARATOR sep
);

  1. 基本查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    SELECT * FROM aa;

    +------+------+
    | id| name |
    +------+------+
    |1 | 10|
    |1 | 20|
    |1 | 20|
    |2 | 20|
    |3 | 200 |
    |3 | 500 |
    +------+------+
    6 rows in set (0.00 sec)
  2. 以id分组,把name字段的值打印在一行,逗号分隔(默认)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SELECT id, GROUP_CONCAT(name) FROM aa GROUP BY id;

    +------+--------------------+
    | id| GROUP_CONCAT(name) |
    +------+--------------------+
    |1 | 10,20,20|
    |2 | 20 |
    |3 | 200,500|
    +------+--------------------+
    3 rows in set (0.00 sec)
  3. 以id分组,把name字段的值打印在一行,分号分隔

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SELECT id, GROUP_CONCAT(name separator ';') FROM aa GROUP BY id;

    +------+----------------------------------+
    | id| GROUP_CONCAT(name separator ';') |
    +------+----------------------------------+
    |1 | 10;20;20 |
    |2 | 20|
    |3 | 200;500 |
    +------+----------------------------------+
    3 rows in set (0.00 sec)
  4. 以id分组,把去冗余的name字段的值打印在一行,逗号分隔

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SELECT id, GROUP_CONCAT(distinct name) FROM aa GROUP BY id;

    +------+-----------------------------+
    | id| GROUP_CONCAT(distinct name) |
    +------+-----------------------------+
    |1 | 10,20|
    |2 | 20 |
    |3 | 200,500 |
    +------+-----------------------------+
    3 rows in set (0.00 sec)
  5. 以id分组,把name字段的值打印在一行,逗号分隔,以name排倒序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    SELECT id, GROUP_CONCAT(name ORDER BY name DESC) FROM aa GROUP BY id;

    +------+---------------------------------------+
    | id| GROUP_CONCAT(name ORDER BY name DESC) |
    +------+---------------------------------------+
    |1 | 20,20,10 |
    |2 | 20|
    |3 | 500,200|
    +------+---------------------------------------+
    3 rows in set (0.00 sec)

GROUP_CONCAT使用及报错分析

函数GROUP_CONCAT,但在使用时,结果报了如下错误。

ERROR 1260 (HY000): Row 17 was cut by GROUP_CONCAT()

原因:GROUP_CONCAT截断了结果,GROUP_CONCAT有个最大长度的限制,超过最大长度就会被截断掉。可以用下面命令查看其内存限制。

1
2
3
4
5
6
7
SELECT @@global.group_concat_max_len;

+-------------------------------+
| @@global.group_concat_max_len |
+-------------------------------+
| 1024 |
+-------------------------------+

1024这就是一般MySQL系统默认的最大长度,解决限制方法有二:
方法一:在MySQL配置文件中加上
group_concat_max_len = 102400 #你要的最大长度

方法二:可以简单一点,执行语句:

1
SET GLOBAL group_concat_max_len=102400;

我是用方法一解决的

对连接要注意的事项

当用多个值连接起来产生一个MD5值、或其他用于确定唯一性的时候,几个值之间一定要加分隔符,否则不同记录可能会产生一样的MD5值。如下表:

NAME    AGE    FAMLY_NUM
张三    51     5
李四    5      15

如果用AGE, FAMLY_NUM来产生一个MD5值来唯一确定这一条记录,在它们之间一定要加分隔符,否则连接结果都是515

mysql禁用root远程登录

1
2
mysql> DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
mysql> FLUSH PRIVILEGES;

创建数据库用户并授权

1
2
3
4
CREATE USER bfg_user IDENTIFIED BY 'gXk9IDpybrJPVMKq';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'bfg_user'@'%';
-- GRANT ALL PRIVILEGES ON *.* TO 'bfg_user'@'%';
FLUSH PRIVILEGES;

创建数据库

在MYSQL中用数据库管理员用户创建了一个db,其他MYSQL用户是暂时看不到的,除非得到数据库管理员用户的授权

1
2
3
4
5
6
7
CREATE DATABASE IF NOT EXISTS bfg_db COLLATE = 'utf8mb4_general_ci' CHARACTER SET = 'utf8mb4';

GRANT ALL ON bfg_db.* TO 'bfg_user'@'%' IDENTIFIED BY 'gXk9IDpybrJPVMKq';

GRANT ALL ON bfg_db.* TO 'bfg_user'@'localhost' IDENTIFIED BY 'gXk9IDpybrJPVMKq';

FLUSH PRIVILEGES;

查看用户的权限

1
2
3
4
5
6
7
mysql> SHOW GRANTS;
+---------------------------------------------------------------------------+
| Grants for canal@% |
+---------------------------------------------------------------------------+
| GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%' |
+---------------------------------------------------------------------------+
1 row in set (0.00 sec)

数据库基本操作

删除数据库

1
DROP DATABASE databaseName;

显示所有数据库

1
SHOW DATABASES;

进入某一个数据库

1
USE databaseName;

显示所有表

1
SHOW TABLES;

查看指定表的结构

1
DESC tableName;

mysql添加、删除主键

1
2
3
4
5
6
7
添加自增长的主键id
ALTER TABLE tb ADD PRIMARY KEY(id);
ALTER TABLE tb CHANGE id id INT(10) NOT NULL AUTO_INCREMENT=1;

删除自增长的主键id,先删除自增长,再删除主键
ALTER TABLE tb CHANGE id id int(10); //删除自增长
ALTER TABLE tb DROP PRIMARY KEY; //删除主建

存储过程

创建插入记录的存储过程:

1
2
3
4
5
6
7
8
9
10
DELIMITER $$

DROP PROCEDURE IF EXISTS `proc_insert_student`$$

CREATE PROCEDURE `proc_insert_student`(_sname VARCHAR(20), _sex TINYINT(4), _age INT)
BEGIN
INSERT INTO student(sname, sex, age) VALUES(_sname, _sex, _age);
END$$

DELIMITER ;

创建统计数据的存储过程:

1
2
3
4
5
6
7
8
9
10
11
DELIMITER $$

DROP PROCEDURE IF EXISTS `proc_count_student`$$

CREATE PROCEDURE `proc_count_student`(OUT cs INT)
BEGIN
# CALL proc_count_student(@cs)
SELECT COUNT(*) INTO cs FROM student;
END$$

DELIMITER ;

查看数据库中的存储过程

1
2
3
4
5
6
7
8
mysql> SHOW PROCEDURE STATUS;
+--------+---------------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| Db | Name | Type | Definer | Modified | Created | Security_type | Comment | character_set_client | collation_connection | Database Collation |
+--------+---------------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
| bfg_db | proc_count_student | PROCEDURE | bfg_user@% | 2019-10-21 17:33:15 | 2019-10-21 17:33:15 | DEFINER | | utf8 | utf8_general_ci | utf8_general_ci |
| bfg_db | proc_insert_student | PROCEDURE | bfg_user@% | 2019-10-21 17:21:38 | 2019-10-21 17:21:38 | DEFINER | | utf8 | utf8_general_ci | utf8_general_ci |
+--------+---------------------+-----------+------------+---------------------+---------------------+---------------+---------+----------------------+----------------------+--------------------+
2 rows in set (0.02 sec)

mysql 删除记录

1
2
DELETE FROM t_user;
DELETE FROM t_user WHERE id > 1;

mysql 仅保留 1000 条记录而删除其他记录

1
2
3
4
5
6
7
8
求取总记录数
SELECT COUNT(*) FROM tb_name;

删除部分记录
DELETE FROM tb_name LIMIT 总记录数-1000

例如,比如一个表有 10000 条记录,现想保留 1000 条数据
DELETE FROM tb_name LIMIT 9000

mysql查找删除重复数据并只保留一条

有表数据如下:

1
2
3
4
5
6
7
8
9
mysql> SELECT * FROM t_user;
+------+-------+------+
| id | name | age |
+------+-------+------+
| 1 | zhang | 21 |
| 2 | li | 22 |
| 3 | zhang | 23 |
+------+-------+------+
3 rows in set (0.00 sec)

从上面可知,name字段有两条相同的记录zhang,我们要删除重复记录,保存id最小的一条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DELETE 
FROM
t_user
WHERE
name IN (SELECT t1.name FROM (SELECT name FROM t_user GROUP BY name HAVING COUNT(*) > 1) t1 )
AND
id NOT IN (SELECT t2.id FROM (SELECT MIN(id) id FROM t_user GROUP BY name HAVING COUNT(*) > 1) t2 );

然后再执行查询语句:
mysql> SELECT * FROM t_user;
+------+-------+------+
| id | name | age |
+------+-------+------+
| 1 | zhang | 21 |
| 2 | li | 22 |
+------+-------+------+
2 rows in set (0.00 sec)

可知,id为3的记录已经被删掉了。

MySQL中CHAR和VARCHAR的区别

CHAR VARCHAR
全称是 CHARACTER 全称是 VARIABLE CHARACTER
存储固定长度的数据,如果不足,会用空格补全,读取的时候可能会调用trim() 按数据的实际长度存储,不会用空格补全
占空间大 占空间小
最大能存储255个字符 最大能存储65535个字符
静态内存分配 动态内存分配
索引效率高 索引效率没CHAR高

VARCHAR存储的实际长度是它的值的长度+1,多出的这一个用于保存它实际使用了多大的长度。

复制表

语法一,会复制完整的表结构(包括索引、主键等),但不包括数据:

1
CREATE TABLE tableName_new LIKE tableName_old;

语法二,只会复制简单的表结构(不包括索引、主键等),但可以包括数据(根据FROM后的WHERE条件):

1
CREATE TABLE tableName_new AS SELECT * FROM tableName_old;

mysql 修改字段定义

下面的关键字COLUMN是可以省略的:

1
2
3
4
ALTER TABLE [表名] MODIFY COLUMN [字段名] [类型];

示例: sys_user表里的user_name字段,由原来长度是10个字符,修改改成40个字符
ALTER TABLE sys_user MODIFY COLUMN user_name VARCHAR(40);

mysql 修改字段名

1
2
3
4
ALTER TABLE [表名] CHANGE [旧字段名] [新字段名] [类型];

示例: sys_user表里的user_name字段,修改改成user_name_new
ALTER TABLE sys_user CHANGE user_name user_name_new VARCHAR(40);

mysql 增加列(字段)

1
2
3
4
5
6
7
8
9
10
11
12
13
ALTER TABLE [表名] ADD [列名] [列类型] [其他属性,如默认值];

示例: sys_user表增加一个地址列,长度为200个字符,默认值为null
ALTER TABLE sys_user ADD address VARCHAR(200) DEFAULT NULL;
ALTER TABLE sys_user ADD address VARCHAR(200) DEFAULT NULL AFTER age;

可以一次增加多个列:
ALTER TABLE sys_user ADD (
sex VARCHAR(2) DEFAULT '男',
tel VARCHAR(11)
);

如果新增加的列有默认值,则SQL执行后,该新增的列会自动获得该值。

mysql 删除列(字段)

语法:

1
ALTER TABLE tableName DROP columnName;

mysql 删除表

语法:

1
DROP TABLE tableName;

mysql 分组统计

表结构和数据如下:

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
CREATE TABLE t_user (
id INT PRIMARY KEY,
name VARCHAR(20),
salary INT,
birthday TIMESTAMP
);


sql> INSERT INTO t_user(id, name, salary, birthday) VALUES (1, "a", 4000, STR_TO_DATE("2018-01-25 09:30:05","%Y-%m-%d %h:%i:%s"));
sql> INSERT INTO t_user(id, name, salary, birthday) VALUES (2, "a", 2000, STR_TO_DATE("2018-01-25 09:30:14","%Y-%m-%d %h:%i:%s"));
sql> INSERT INTO t_user(id, name, salary, birthday) VALUES (3, "a", 3000, STR_TO_DATE("2018-01-25 09:30:10","%Y-%m-%d %h:%i:%s"));
sql> INSERT INTO t_user(id, name, salary, birthday) VALUES (4, "b", 6000, STR_TO_DATE("2018-01-25 09:30:29","%Y-%m-%d %h:%i:%s"));
sql> INSERT INTO t_user(id, name, salary, birthday) VALUES (5, "b", 5000, STR_TO_DATE("2018-01-25 09:30:24","%Y-%m-%d %h:%i:%s"));


sql> SELECT * FROM t_user;
+----+------+--------+---------------------+
| id | name | salary | birthday |
+----+------+--------+---------------------+
| 1 | a | 4000 | 2018-01-25 09:30:05 |
| 2 | a | 2000 | 2018-01-25 09:30:14 |
| 3 | a | 3000 | 2018-01-25 09:30:10 |
| 4 | b | 6000 | 2018-01-25 09:30:29 |
| 5 | b | 5000 | 2018-01-25 09:30:24 |
+----+------+--------+---------------------+

  1. 按用户名分组,查询salary最大的记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    sql> SELECT id, name, MAX(salary) FROM t_user GROUP BY name;

    +----+------+-------------+
    | id | name | MAX(salary) |
    +----+------+-------------+
    | 1 | a | 4000 |
    | 4 | b | 6000 |
    +----+------+-------------+


    或者
    sql > SELECT * FROM (SELECT * FROM t_user ORDER BY salary DESC) t GROUP BY t.name;

    +----+------+--------+---------------------+
    | id | name | salary | birthday |
    +----+------+--------+---------------------+
    | 1 | a | 4000 | 2018-01-25 09:30:05 |
    | 4 | b | 6000 | 2018-01-25 09:30:29 |
    +----+------+--------+---------------------+
  2. 按用户名分组,取生日最小的记录

    1
    2
    3
    4
    5
    6
    7
    8
    sql> SELECT * FROM (SELECT * FROM t_user ORDER BY birthday DESC) t GROUP BY t.name;

    +----+------+--------+---------------------+
    | id | name | salary | birthday |
    +----+------+--------+---------------------+
    | 2 | a | 2000 | 2018-01-25 09:30:14 |
    | 4 | b | 6000 | 2018-01-25 09:30:29 |
    +----+------+--------+---------------------+

MySQL数据库备份和还原常用的命令

一、备份命令

如果只需要导出表的结构,可以使用mysqldump的-d选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1、备份MySQL数据库的命令,备份指定的整个库
mysqldump -hhostname -uusername -ppassword databasename > ~/backupfile.sql

2、备份MySQL数据库为带删除表的格式,能够让该备份覆盖已有数据库而不需要手动删除原有数据库
mysqldump --add-drop-table -hhostname -uusername -ppassword databasename > ~/backupfile.sql

3、直接将MySQL数据库压缩备份
mysqldump -hhostname -uusername -ppassword databasename | gzip > ~/backupfile.sql.gz

4、备份MySQL数据库某个(些)表
mysqldump -hhostname -uusername -ppassword databasename specific_table1 [specific_table2] > ~/backupfile.sql

5、同时备份多个MySQL数据库
mysqldump -hhostname -uusername -ppassword --databases databasename1 databasename2 databasename3 > ~/multibackupfile.sql

6、仅仅备份数据库结构
mysqldump -hhostname -uusername -ppassword -d --databases databasename1 databasename2 databasename3 > ~/structurebackupfile.sql

7、备份服务器上所有数据库
mysqldump -hhostname -uusername -ppassword --all-databases > ~/allbackupfile.sql

8、备份MySQL数据库某个表中指定条件的数据
mysqldump -hhostname -uusername -ppassword databasename specific_table1 --where='age > 10' > ~/backupfile.sql

二、还原命令

1
2
3
4
5
6
7
8
1、还原MySQL数据库的命令
mysql -hhostname -uusername -ppassword databasename < ~/backupfile.sql

2、还原压缩的MySQL数据库
gunzip < ~/backupfile.sql.gz | mysql -uusername -ppassword databasename

3、将数据库转移到新服务器
mysqldump -uusername -ppassword databasename | mysql -host=*.*.*.* -C databasename

SELECT INTO

除此之外,你还可以使用SELECT INTO命令来导出数据,它与mysqldump的区别是:

  1. mysqldump: 导出的文本包括了数据库的结构和记录;
  2. SELECT INTO: 导出的文本只有记录,并且,一般要数据库的管理员帐号,例如root才能执行;

SELECT INTO 有两种使用方式:

1
2
3
4
5
6
7
方式一:
mysql> SELECT * FROM t_user INTO OUTFILE 't_user.txt';
Query OK, 2 rows affected (0.16 sec)

方式二:
mysql> SELECT * INTO OUTFILE 't_user.txt' FROM t_user;
Query OK, 2 rows affected (0.00 sec)

导出的数据一般在数据库的安装目录下,你也可以使用find / -name t_user.txt来查找到

ERROR 3 (HY000): Error writing file ‘/tmp/MYeaZGpS’ (Errcode: 28 - No space left on device)

根据提示,是说mysql中/tmp使用的磁盘空间不足。
在MYSQL命令行下执行:

1
2
3
4
5
6
7
mysql> SHOW VARIABLES LIKE 'tmpdir';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| tmpdir | /tmp |
+---------------+-------+
1 row in set (0.01 sec)

可以看到MYSQL的tmpdir是使用/tmp这个临时目录的,使用df -h看下/tmp的空间,确实不足。我们修改tmpdir所指定的目录,我们在空间比较大的分区上面建一个目录。

1
2
3
$ cd /opt/data
$ mkdir mysqltmp
$ chmod a+w mysqltmp

然后修改my.cnf配置文件在其中修改tmpdir

1
2
3
4
5
6
$ whereis my.cnf
$ cd {my.cnf所在的目录}
$ vi my.cnf

修改的内容如下
tmpdir = /opt/data/mysqltmp

重启mysql

1
$ /etc/init.d/mysqld restart    # 不同Linux系统,命令可能不同

在MYSQL命令行中查看下是否生效

1
2
3
4
5
6
7
mysql> SHOW VARIABLES LIKE 'tmpdir';
+---------------+--------------------+
| Variable_name | Value |
+---------------+--------------------+
| tmpdir | /opt/data/mysqltmp |
+---------------+--------------------+
1 row in set (0.00 sec)

问题解决。

读一致性

mysql有一个表,有1000W条记录。在9:00的时候A用户对表进行查询,查询需10分钟才能完成,表没索引,FULL SCAN,表中某条记录的值为100。在9:05分的时候B对表进行了UPDATE操作,将记录的值修改为200。那么在9:10的时候,A获致到的是100还是200?结果是100。因为这是读一致性的问题,在A查的时候,它看到的是整个表在那一刻的快照的数据,返回的是那一刻的结果。不过,它有可能会抛出 snapshot too old 这个异常。

mysql 修改表名

1
ALTER TABLE table_name RENAME TO new_table_name;

INSERT INTO插入数据

如果不指定插入的列名,则VALUES的值必须与表的所有列名一一对应

1
2
3
4
5
6
7
8
9
CREATE TABLE t_user (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20),
age INT
);

INSERT INTO t_user (name) VALUES ('Scott');
INSERT INTO t_user VALUES (NULL, 'Scott', 20);
INSERT INTO t_user VALUES (NULL, 'Scott', 20), (NULL, 'tiger', 21);

MYSQL插入数据时忽略重复数据的方法

使用IGNORE关键字,示例:

1
2
3
4
INSERT IGNORE INTO tableName(column_list)
VALUES (value_list),
(value_list),
...

将一个数据库中的表导入到另一个数据库中的表

将数据库A中的表a的数据导入到数据库B中的表b中。

1
2
3
INSERT [IGNORE] INTO 数据库B.`表名b` SELECT * FROM 数据库A.`表名a`;
INSERT [IGNORE] INTO 数据库B.`表名b` SELECT col1, col2, col3 FROM 数据库A.`表名a`;
INSERT [IGNORE] INTO 数据库B.`表名b`(col1, col2, col3) SELECT col1, col2, col3 FROM 数据库A.`表名a`;

MySQL千万级数据快速分页

数据量少的时候,可以这样写:

1
SELECT * FROM tableName ORDER BY id LIMIT 1000, 10;

但在数据量达到百万级,甚至千万级的时候,这样写会慢死:

1
SELECT * FROM tableName ORDER BY id LIMIT 1000000, 10;

可能耗费几十秒,网上有很多优化的方法是这样的:

1
SELECT * FROM tableName WHERE id >= (SELECT id FROM tableName LIMIT 1000000, 1) LIMIT 10;

是的,速度提升到0.x秒了,看样子还行。但,还不是完美的!下面这句才是完美的:

1
SELECT * FROM tableName WHERE id BETWEEN 1000000 AND 1000010;

比上面那句,还要再快5至10倍

另外,如果需要查询id不是连续的一段,最佳的方法就是先找出id,然后用IN查询,代码如下:

1
SELECT * FROM tableName WHERE id IN (10000, 100000, 1000000, ...);

如果查询的字段是一较长字符串的时候,表设计时要为该字段多加一个字段。如,存储网址的字段,查询的时候,不要直接查询该字符串,这样效率低下,应该查询该字符串的CRC32或MD5。

mysql 查询一个字符串字段最长的一条记录

参考:http://stackoverflow.com/questions/2357620/maxlengthfield-in-mysql

1
2
3
SELECT name, LENGTH(name)
FROM my_table
WHERE LENGTH(name) = (SELECT MAX(LENGTH(name)) FROM my_table);

skip-grant-tables 参数的使用

这是个非常有用的mysql启动参数

在my.cnf文件中增加一行:

1
2
[mysqld]
skip-grant-tables

或者以命令行参数启动mysql:

/usr/bin/mysqld_safe --skip-grant-tables &

登陆mysql修改管理员密码:

1
2
3
4
USE mysql;
UPDATE user SET password=password('root') WHERE user='root';
FLUSH PRIVILEGES;
EXIT;

重启mysql

DELETE和TRUNCATE的区别

TRUNCATE TABLE 属于DDL,它虽然和对整张表执行DELETE操作产生的效果差不多,但是它是不能回滚的。

三大范式

第一范式(1NF):在关系模式R中的每一个具体关系r,必须要有主键,并且每个属性值都是不可再分的最小数据单位,则称R是第一范式的关系;
第二范式(2NF):如果关系模式R中的所有非主属性都完全依赖于主关键字,则称关系R是属于第二范式的;
第三范式(3NF):关系模式R中的非主关键字不能依赖于其他非主关键字。即非主关键字之间不能有函数(传递)依赖关系,则称关系R是属于第三范式的。

VARCHAR类型的说明

在官方手册中,VARCHAR类型最大支持65535,单位是字节,但实际上。在创建表的时候VARCHAR(N)中的N,指的是字符的长度。但是,如果表的字符集不同,能支持的最大长度也不同。

  1. 表的CHARSET=latin1时,能使用VARCHAR(65532);
  2. 表的CHARSET=GBK时,能使用VARCHAR(32767);
  3. 表的CHARSET=UTF8,能使用VARCHAR(21845);
  4. 如果表的SQL_MODE为非严格模式,则表的CHARSET=latin1时,或者能使用VARCHAR(65535)成功创建表,但是可能会有warning。warning可能是数据库将那列自动转换为TEXT类型了;
  5. 还有一个需要注意的地方是,65535长度是指表中所有VARCHAR列的长度总和。如果列的长度总和超出这个限制,依然无法创建表。

CHAR类型的说明

CHAR(N)中的N指的是字符,对于多字节字符编码的CHAR数据类型,在InnoDB存储引擎中,会将其视为变长类型。对于未能占满长度的字符,还是填充0X20,也即是空格。

在多字节字符集的情况下,CHAR和VARCHAR的实际行存储基本是没有区别的。

强制使用索引

在查询的过程中可以使用FORCE INDEX来强制查询优化器使用指定的索引,例如表t_user有索引idx_name,则我们可以这样查询:

1
SELECT * FROM t_user FORCE INDEX(idx_name) WHERE name='Tim' AND age=22;

索引提示

在查询的过程中可以使用USE INDEX来显式地告诉查询优化器使用哪个索引,例如表t_user有索引idx_name,则我们可以这样查询:

1
SELECT * FROM t_user USE INDEX(idx_name) WHERE name='Tim' AND age=22;

主要在以下2种情况下要使用索引提示:

  1. MYSQL数据库的优化器错误地选择了某个索引,导致SQL语句运行得很慢;
  2. 某SQL语句可以选择的索引非常多,优化器选择执行计划时间的开销大于SQL语句本身。

USE INDEX只是告诉优化器可以选择该索引,但实际上优化器可能还是会再根据自已的判断进行选择,除非使用FORCE INDEX

对数据库执行了一个删除操作,实际上并不会删除索引中的数据,相反还会在对应的DELETED表中插入记录,因此随着应用程序的运行,索引会变得非常大。即使索引中的有些数据已经被删除,查询也不会选择这类记录。InnoDb存储引擎,可以在必要时手动将已经删除的记录在索引中删除,命令是 OPTIMIZE TABLE。它还会对Cardinality进行统计,因此可以关闭它的统计。

1
2
mysql> SET GLOBAL innodb_optimize_fulltext_only=1;
mysql> OPTIMIZE TABLE table_name;

锁问题

锁问题有三个:

  1. 脏读:首先了解一下脏数据,脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交。如果读到了脏数据,即一个事务可以读到另外一个事务中未提交的数据,这显然违反了数据库的隔离性。而对脏页的读取,是非常正常的。脏页是因为数据库实例内存和磁盘的异步造成的,这并不影响数据的一致性,因为内存中的数据最终是要刷新回磁盘的。

  2. 不可重复读:在同一个事务中多次读取同一个表,在多次读取之间,还有另外一个事务对该表进行了DML,从而导致多次读取到的数据可能不一致。与脏读的区别是:脏读读取到的是未提交的数据,而不可重复读读到的是已经提交的数据。这违反了数据库事务的一致性。

  3. 丢失更新:就是一个事务的更新操作,会被另一个事务的更新操作所覆盖,从而导致数据的不一致。可以将事务的处理模式变成串行的,这样来避免。在更新数据的时候,特别是敏感数据的时候,一定要先加一个排他X锁,这样也方便对帐户余额进行先检查,再更新。而不是仅仅学会了简单的SELECT、UPDATE操作就开始处理数据。

    1
    SELECT cash FROM account WHERE user='user_name' FOR UPDATE;

事务的ACID特性

事务是作为一个逻辑单元执行的一系列操作,一个逻辑工作单元必须有四个属性,

称为 ACID(原子性、一致性、隔离性和持久性)属性,只有这样才能成为一个事务。

原子性(Atomicity)
整个事务中的所有操作,要么全部成功执行,要么全部失败。

一致性(Consistency)
事务提交前后,表的所有约束条件都保持完好。对表的修改符合所有的Keys、数据类型、Checks和触发器的约束,没有约束被破坏。

隔离性(Isolation)
一个事务,无法访问到另一个事务未提交的数据。

持久性(Durability)
事务成功执行后,对数据的修改会持久保存在数据库中,不会被回滚,就算系统死机。

事务的隔离级别

对于InnoDB存储引擎而言,它默认的事务隔离级别为REPEATABLE READ,完全遵循和满足事务的ACID特性。而Oracle、sqlServer则是READ COMMITTED。

SQL标准定义了4个隔离级别:
READ UNCOMMITTED:会出现脏读、不可重复读、幻读(隔离级别最低,并发性能高)
READ COMMITTED:会出现不可重复读、幻读问题(锁定正在读取的行)
REPEATABLE READ:会出幻读(锁定所读取的所有行)
SERIALIZABLE:保证所有的情况不会发生(锁表)

可以在数据库启动的时候设置它的默认隔离级别:

1
2
[mysqld]
transaction-isolation=READ-COMMITTED

查看当前会话的事务隔离级别:

1
mysql> SELECT @@tx_isolation;

分布式事务

InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与事务要么都提交,要么都ROLLBACK,这对于事务原有的ACID要求又有了提高。另外,在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。
XA事务允许不同数据库之间的分布式事务,如一台服务器是MySQL数据库,另一台是Oracle数据库,只要参与在全局事务中的每个节点都支持XA事务。分布式事务可能在银行系统的转帐中比较常见。可以通过变量查看数据库是否启用了XA事务支持(默认为ON):

1
mysql> SHOW VARIABLES LIKE 'innodb_support_xa';

MySQL数据库是自动提交的。当用户显式的使用命令START TRANSACTIONBEGIN来开启一个事务时,MySQL会自动地执行SET AUTOCOMMIT=0命令,并在COMMITROLLBACK结束一个事务后,再执行SET AUTOCOMMIT=1

对长事务的处理,是将长事务分解为多个小事务来分批处理。在应用程序中,最好是将事务的START TRANSACTION、COMMIT、ROLLBACK操作交给程序端来控制,而不是在存储过程中。

备份与恢复

Hot Backup 热备: 在数据库运行中直接备份,对运行中的数据库操作没有任何的影响;
Cold Backup 冷备: 在数据库停机状态下进行备份,一般备份数据库的物理文件;
Warm Backup 温备:也是在数据库运行中进行备份,但是会对当前的数据库操作有所影响,例如它会加一个全局读锁以保证备份数据的一致性。

对于mysqldump备份工具来说,可以通过添加--single-transaction选项获得InnoDB存储引擎的一致性备份,此时的备份是在一个执行时间很长的事务中完成的。请务必加上此选项。

mysqldump不能导出视图。免费好用的开源热备份工具有XtraBackup。

连接到mysql

1
2
$ mysql -h192.168.1.100 -uscoot -p
$ mysql -h192.168.1.100 -uscoot -ptiger

不登录MYSQL来执行查询

可以在操作系统命令行下通过-e参数实现,例如查询test库下的表t_user:

1
2
3
4
5
6
7
8
9
10
$ mysql -h192.168.1.100 -uscoot -ptiger test -e "SELECT * FROM t_user"

+----+-------+------+
| id | name | age |
+----+-------+------+
| 1 | zhang | 21 |
| 2 | zhang | 23 |
| 4 | zhang | 21 |
| 5 | zhang | 21 |
+----+-------+------+

说明:

-e, --execute=name  Execute command and quit. (Disables --force and history file.)

约束

概述:

  1. 通过约束可以更好的保证数据表里数据的完整性;
  2. 约束是在表上强制执行的数据校验规则,约束主要保证数据的完整性;
  3. 当表中的数据存在相互依赖时,可以通过约束保护相关的数据不被删除。

MYSQL中支持以下五类约束:

  1. NOT NULL:非空约束,指定某列不能为空;
  2. UNIQUE:唯一约束,指定某列或者几列组合不能重复,允许空;
  3. PRIMARY KEY:主键,指定该列的值可以唯一的表示每条记录;
  4. FOREIGN KEY:外键,指定该行记录从属于主表中的一条记录,主要用于保证参照完整性;
  5. CHECK:检查,指定一个布尔表达式,用于指定对应列的值必须满足该表达式。

NOT NULL约束

如果我们向NOT NULL的字段插入一下NULL值,MySQL数据库会将其更改为0再进行插入。我们可以通过设置SQL_MODE来严格审核输入参数。
所有数据类型值都可以为null,如:int,float等。空字符串不等于null,0也不等于null。

UNIQUE约束

UNIQUE约束用于保证指定列或指定列的组合不允许出现重复,但可以允许出现多个null值。同一张表内可以建多个UNIQUE约束。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE t_user (
id INT NOT NULL,
userName VARCHAR(20) UNIQUE
);

CREATE TABLE t_user (
id INT NOT NULL,
userName VARCHAR(20),
CONSTRAINT index_uk UNIQUE(id, userName)
);

PRIMARY KEY约束

主键约束相当于唯一约束和非空约束,每个表中最多允许一个主键。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE t_user (
id INT PRIMARY KEY,
userName VARCHAR(20)
);

CREATE TABLE t_user (
id INT NOT NULL,
userName VARCHAR(20),
CONSTRAINT index_pk PRIMARY KEY(id)
);

FOREIGN KEY约束

概述:

  1. 外键约束主要用于保证一个或两个数据表之间的参照完整性,外键构建于一个表的两个字段或者两个表的两个字段之间;
  2. 建立外键时mysql也会为该列建立索引;
  3. 子表外键列的值必须在主表被参照列值的范围之内,或者为空;
  4. 为了保证子表参照的主表存在,通常应先建主表;
  5. 使用列级约束语法建立外键约束直接使用REFERENCES关键字。指定该列参照的哪个主表,以及参照主表的哪一列。
1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE teacher (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20)
);

CREATE TABLE student (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20),
teacher_id INT,
FOREIGN KEY(teacher_id) REFERENCES teacher(id)
);

CHECK约束

CHECK约束在建表的列定义后增加逻辑表达式即可。

1
2
3
4
5
6
CREATE TABLE t_user (
id INT AUTO_INCREMENT PRIMARY KEY,
userName VARCHAR(20),
age INT,
CHECK(age > 0)
);

目前所有MYSQL存储引擎并没有实现CHECK约束,也就是说,上面的建表语句正确,但约束无效。
https://dev.mysql.com/doc/refman/5.7/en/create-table.html#create-table-indexes-keys

The CHECK clause is parsed but ignored by all storage engines.

COUNT(1)、COUNT(*)、COUNT(pk)的区别

下面两句产生的结果是一样的:
COUNT(*) 统计表中有多少行,包括字段为NULL的行
COUNT(1) 统计表中有多少行,包括字段为NULL的行

当pk为主键的时候,因为主键不允许为NULL,所以
COUNT(pk) 同样是,统计表中有多少行,包括字段为NULL的行

但是,如果pk代表的字段允许为NULL,那么将产生不同的结果
COUNT(pk) 统计表中有多少行,不包括pk为NULL的行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql> SELECT * FROM t_user;
+----+------+--------+---------------------+
| id | name | salary | birthday |
+----+------+--------+---------------------+
| 1 | a | 4000 | 2018-01-25 09:30:05 |
| 2 | a | 2000 | 2018-01-25 09:30:14 |
| 3 | a | 3000 | 2018-01-25 09:30:10 |
| 4 | b | 6000 | 2018-01-25 09:30:29 |
| 5 | b | 5000 | 2018-01-25 09:30:24 |
| 6 | NULL | 6000 | 2018-01-25 09:30:29 |
+----+------+--------+---------------------+
6 rows in set (0.00 sec)

sql> SELECT COUNT(*), COUNT(1), COUNT(name) FROM t_user;
+----------+----------+-------------+
| COUNT(*) | COUNT(1) | COUNT(name) |
+----------+----------+-------------+
| 6 | 6 | 5 |
+----------+----------+-------------+

通常,建议使用COUNT(*),因为它是SQL推荐的,SQL会自动优化到一个字段。

执行效率:

  1. 当pk为主键时,COUNT(pk)比COUNT(1)快,否则COUNT(1)比COUNT(pk)快;
  2. 当表有多个列时,如果没有主键,则COUNT(1)比COUNT(*)快; 如果有主键,则COUNT(pk)的效率是最优的;
  3. 当表只有一个列时,COUNT(*)最优。

left join, right join, inner join, join区别

有如下表:

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
CREATE TABLE t_user (
id INT NOT NULL,
name VARCHAR(20)
);

CREATE TABLE t_contact (
id INT NOT NULL COMMENT 't_user的主键 id',
card VARCHAR(20)
);

INSERT INTO t_user VALUES (1, 'Scott'),(2, 'Tiger'),(3, 'Larry');
INSERT INTO t_contact VALUES (1, 'A'),(1, 'B'),(2, 'C'),(4, 'D');


MySQL [test]> select * from t_user;
+----+-------+
| id | name |
+----+-------+
| 1 | Scott |
| 2 | Tiger |
| 3 | Larry |
+----+-------+
3 rows in set (0.00 sec)

MySQL [test]> select * from t_contact;
+----+------+
| id | card |
+----+------+
| 1 | A |
| 1 | B |
| 2 | C |
| 4 | D |
+----+------+

join 查询

1
2
3
4
5
6
7
8
MySQL [test]> select * from t_user tu join t_contact tc on tu.id=tc.id;
+----+-------+----+------+
| id | name | id | card |
+----+-------+----+------+
| 1 | Scott | 1 | A |
| 1 | Scott | 1 | B |
| 2 | Tiger | 2 | C |
+----+-------+----+------+

inner join 查询
只返回两个表中联结字段相等的行

1
2
3
4
5
6
7
8
MySQL [test]> select * from t_user tu inner join t_contact tc on tu.id=tc.id;
+----+-------+----+------+
| id | name | id | card |
+----+-------+----+------+
| 1 | Scott | 1 | A |
| 1 | Scott | 1 | B |
| 2 | Tiger | 2 | C |
+----+-------+----+------+

left join 查询
返回左表中的所有记录和右表中联结字段相等的记录

1
2
3
4
5
6
7
8
9
MySQL [test]> select * from t_user tu left join t_contact tc on tu.id=tc.id;
+----+-------+------+------+
| id | name | id | card |
+----+-------+------+------+
| 1 | Scott | 1 | A |
| 1 | Scott | 1 | B |
| 2 | Tiger | 2 | C |
| 3 | Larry | NULL | NULL |
+----+-------+------+------+

right join 查询
返回右表中的所有记录和左表中联结字段相等的记录

1
2
3
4
5
6
7
8
9
MySQL [test]> select * from t_user tu right join t_contact tc on tu.id=tc.id;
+------+-------+----+------+
| id | name | id | card |
+------+-------+----+------+
| 1 | Scott | 1 | A |
| 1 | Scott | 1 | B |
| 2 | Tiger | 2 | C |
| NULL | NULL | 4 | D |
+------+-------+----+------+

查询满足多个属性的产品列表

像淘宝中搜索满足多个条件的产品列表,准备数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE t_product (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL COMMENT '产品名'
);

CREATE TABLE t_product_property (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL COMMENT '产品id',
property_name VARCHAR(20) NOT NULL COMMENT '属性名'
);


INSERT INTO t_product(id, name) VALUES (1,'car'),(2,'plane');
INSERT INTO t_product_property(id, product_id, property_name)
VALUES(1,1,'drive'),(2,1,'black'),(3,1,'benz'),(4,2,'fly'),(5,2,'white'),(6,2,'boeing');

匹配一个属性:drive

1
2
3
4
5
6
SELECT * FROM t_product WHERE id IN
(
SELECT p.product_id FROM (
t_product_property p
) WHERE p.id = 1
);

匹配两个属性:drive, black

1
2
3
4
5
6
7
SELECT * FROM t_product WHERE id IN
(
SELECT p.product_id FROM (
t_product_property p,
(SELECT id, product_id FROM t_product_property) p2
) WHERE p.id = 1 AND p2.id = 2 AND p.product_id = p2.product_id
);

匹配三个属性:drive, black, benz

1
2
3
4
5
6
7
8
SELECT * FROM t_product WHERE id IN
(
SELECT p.product_id FROM (
t_product_property p,
(SELECT id, product_id FROM t_product_property) p2,
(SELECT id, product_id FROM t_product_property) p3
) WHERE p.id = 1 AND p2.id = 2 AND p3.id = 3 AND p.product_id = p2.product_id AND p.product_id = p3.product_id
);

命令行连接mysql

mysql -hip_address -uuser_name -Pport -ppassword db_name

如果-p后面的密码中有特殊字符,就要加个\来转义
mysql -h192.168.1.100 -uroot -P3306 -pT3O\$8yKl%aRi user_db

mysql报错ERROR 1064 (42000)

原因是使用了mysql的保留字。如果表的字段使用了mysql的保留字,在查询的时候要用反引号将其引起来。
SELECT * FROM t_user WHERE key = ‘abc’;

mysql update

单表更新语法:

1
2
3
4
5
6
7
UPDATE [LOW_PRIORITY] [IGNORE] table_name
SET
column_name1 = expr1,
column_name2 = expr2,
...
[WHERE
condition];

也可以用从其他表SELECT出来的数据来SET值

1
2
3
4
5
UPDATE table_name
SET
column_name1 = (SELECT column_name1 FROM table_name2 WHERE column_name2 = 'h' ORDER BY RAND() LIMIT 1)
WHERE
column_name1 IS NULL;

连表更新语法:

1
2
3
4
5
6
UPDATE T1, T2
[INNER JOIN | LEFT JOIN] T1 ON T1.C1 = T2. C1
SET
T1.C2 = T2.C2,
T2.C3 = expr
WHERE condition

示例:
Suppose you want to adjust the salary of employees based on their performance.

1
2
3
4
UPDATE employees e
INNER JOIN merits m ON e.performance = m.performance
SET
e.salary = e.salary + e.salary * m.percentage;

The UPDATE LEFT JOIN statement basically updates a row in a table when it does not have a corresponding row in another table.

For example, you can increase the salary for a new hire by 1.5% using the following statement:

1
2
3
4
5
6
UPDATE employees e
LEFT JOIN merits m ON e.performance = m.performance
SET
e.salary = e.salary + e.salary * 0.015
WHERE
m.percentage IS NULL;

FOREIGN KEY引用导致无法删表

这时候,只要按如下方式删除,即可。

1
2
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS table_name;

对MySQL数据库性能的测试工具

这里有2款比较好的工具:sysbench和mysql-tpcc

主要测试:
CPU性能
磁盘IO性能
调度程序性能
内存分配及传输速度
POSIX线程性能
数据库OLTP基准测试

zookeeper 集群版安装方法

计划在一台Ubuntu Linux服务器上部署3台zookeeper服务器,分别为server1, server2, server3

因为三台zookeeper服务器的配置都差不多,所以我们先设置好一台server1的配置,再将其复制成server2, server3并修改其中的配置即可。

1.建目录,如下:

1
2
$ mkdir -p /home/hewentian/ProjectD/zookeeperCluster/server1
$ mkdir -p /home/hewentian/ProjectD/zookeeperCluster/server1/data

2.将zookeeper-3.4.6.tar.gz放到/home/hewentian/ProjectD/zookeeperCluster/server1目录下,并执行如下脚本解压

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server1
$ tar xzvf zookeeper-3.4.6.tar.gz

3.解压后得到zookeeper-3.4.6文件夹,进入conf目录

1
$ cd zookeeper-3.4.6/conf/

4.执行如下命令,新建一个配置文件zoo.cfg

1
$ cp zoo_sample.cfg zoo.cfg

5.修改zoo.cfg并在其中修改如下内容:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/hewentian/ProjectD/zookeeperCluster/server1/data # 这里必须为绝对路径,否则有可能无法启动
clientPort=2181                 # 这台服务器的端口为2181(即默认值),当部署为真集群的时候各个节点的端口都是2181
server.1=127.0.0.1:2888:3888    # 当部署成真集群的时候:
server.2=127.0.0.1:2889:3889    # server.1、server.2和server.3后面的IP要修改成各个节点的真实IP或者域名
server.3=127.0.0.1:2890:3890    # 并且server.2、server.3后面的两个端口要和server.1的是一样的,都是2888:3888

6.在/home/hewentian/ProjectD/zookeeperCluster/server1/data目录下建myid文件并在其中输入1,只输入1,代表server.1

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server1/data
$ vi myid

这样第一台服务器已经配置完毕。

7.接下来我们将server1复制为server2, server3

1
2
3
$ cd /home/hewentian/ProjectD/zookeeperCluster
$ cp -r server1 server2
$ cp -r server1 server3

8.将server2/data目录下的myid的内容修改为2

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server2/data
$ vi myid

同理,将将server3/data目录下的myid的内容修改为3

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server3/data
$ vi myid

9.修改server2的配置文件

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server2/zookeeper-3.4.6/conf/
$ vi zoo.cfg

仅修改两处地方即可,要修改的地方如下:

dataDir=/home/hewentian/ProjectD/zookeeperCluster/server2/data  # 这里是数据保存的位置
clientPort=2182                                                 # 这台服务器的端口为2182

同理,修改server3的配置文件

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server3/zookeeper-3.4.6/conf/
$ vi zoo.cfg

仅修改两处地方即可,要修改的地方如下:

dataDir=/home/hewentian/ProjectD/zookeeperCluster/server3/data  # 这里是数据保存的位置
clientPort=2183                                                 # 这台服务器的端口为2183

10.到目前为此,我们已经将3台zookeeper服务器都配置好了。接下来,我们要将他们都启动

启动server1

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server1/zookeeper-3.4.6/bin/
$ ./zkServer.sh start

启动server2

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server2/zookeeper-3.4.6/bin/
$ ./zkServer.sh start

启动server3

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server3/zookeeper-3.4.6/bin/
$ ./zkServer.sh start

11.当三台服务器都启动好了,我们分别连到server1、server2、server3:

连接到server1

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server1/zookeeper-3.4.6/bin/
$ ./zkCli.sh -server 127.0.0.1:2181

连接到server2

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server2/zookeeper-3.4.6/bin/
$ ./zkCli.sh -server 127.0.0.1:2182

连接到server1

1
2
$ cd /home/hewentian/ProjectD/zookeeperCluster/server3/zookeeper-3.4.6/bin/
$ ./zkCli.sh -server 127.0.0.1:2183

这样你在server1中所作的修改,都会同步到server2, server3
例如你在server1中

1
$ create /zk_test_cluster my_data_cluster

你在server2, server3的客户端用

1
$ ls /

都会看到节点zk_test_cluster

12. 查看运行状态

1
2
3
4
5
6
7
8
9
10
11
$ cd /home/hewentian/ProjectD/zookeeperCluster/server2/zookeeper-3.4.6/
$ ./bin/zkServer.sh status
JMX enabled by default
Using config: /home/hewentian/ProjectD/zookeeperCluster/server2/zookeeper-3.4.6/bin/../conf/zoo.cfg
Mode: leader

$ cd /home/hewentian/ProjectD/zookeeperCluster/server3/zookeeper-3.4.6/
$ ./bin/zkServer.sh status
JMX enabled by default
Using config: /home/hewentian/ProjectD/zookeeperCluster/server3/zookeeper-3.4.6/bin/../conf/zoo.cfg
Mode: follower

可以看到server2是leader,其他两台是follower。

集群部署结束。

zookeeper三种角式

leader:负责进行投票的发起和决议,更新系统状态;
follower:负责接受客户端的请求并向客户端返回结果,在选主过程中参与投票;
observer:接受客户端的连接,将写请求转发给leader,但不参与投票,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度。follower和observer统称为leaner。
client:客户端,请求发起方。

配置observer比较简单,只要在zoo.cfg中将要配置成为observer的机器加个后缀即可,如下:

server.3=127.0.0.1:2890:3890:observer

zookeeper的节点类型

zookeeper有4种节点类型:

  1. PERSISTENT 持久化节点
  2. PERSISTENT_SEQUENTIAL 顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
  3. EPHEMERAL 临时节点, 客户端session超时这类节点就会被自动删除
  4. EPHEMERAL_SEQUENTIAL 临时自动编号节点

注意:EPHEMERAL类型的节点不能有子节点

参数说明:

  1. tickTime:zookeeper中使用的基本时间单位,毫秒值;
  2. initLimit:由于zookeeper集群中包含多台server,其中一台为leader,其余的为follower。initLimit参数配置初始化连接时,follower和leader之间的最长心跳时间,此时该参数设置为5,说明时间限制为5倍tickTime, 即:5 * 2000 = 10000ms = 10s;
  3. syncLimit:该参数配置leader和follower之间发送消息,请求和应答的最大时间长度。此时该参数设置为2,说明时间限制为2倍tickTime,即4000ms;
  4. dataDir: 数据存放目录,可以是任意目录;
  5. dataLogDir: log目录,同样可以是任意目录。如果没有设置该参数,将使用和dataDir相同的设置;
  6. clientPort: 监听client连接的端口号;
  7. server.X=A:B:C:其中X是一个数字,表示这是第几号server。A是该server所在的IP地址;B配置该server和集群中的leader交换消息所使用的端口;C配置选举leader时所使用的端口。由于配置的是伪集群模式,所以各个server的B,C参数必须不同。

zookeeper能帮我们做什么

  1. Hadoop使用zookeeper的事件处理确保整个集群只有一个NameNode存储配置信息等;
  2. HBase使用zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机、存储访问控制列表等。

一些要注意的

  1. 注册的Watcher消息只会通知一次;
  2. 节点需要奇数个的原因有二:(1)容错和偶数是一样的,所以没必要多台;(2)防止脑裂;
  3. zookeeper两种模式:恢复模式(选主)和广播模式(Zab原子广播更新数据);
  4. zookeeper的4个特点:最终一致性、可靠性、原子性、顺序性(因为增删改都发给Leader);
  5. zookeeper的数据存放在内存中,但是会定期flush到磁盘dataDir的目录中。

通过java api使用zookeeper的例子可以参见这里: ZookeeperUtil.javazookeeper实现分布式锁、rmi高可用

zookeeper 单机版安装方法

下面演示在 Ubuntu 16.04 LTS 下面安装 zookeeper

zookeeper-3.4.6.tar.gz放到一个目录下,如/home/hewentian/ProjectD
执行如下脚本解压

1
2
$ cd /home/hewentian/ProjectD
$ tar xzvf zookeeper-3.4.6.tar.gz

解压后得到zookeeper-3.4.6文件夹,进入conf目录

1
$ cd zookeeper-3.4.6/conf/

执行如下命令,新建一个配置文件zoo.cfg

1
$ cp zoo_sample.cfg zoo.cfg

修改zoo.cfg并在其中修改如下内容:

tickTime=2000
dataDir=/home/hewentian/ProjectD/zookeeper-3.4.6/data
clientPort=2181

解压后的zookeeper-3.4.6目录下是没有data这个文件夹的,要执行如下命令创建

1
2
$ cd /home/hewentian/ProjectD/zookeeper-3.4.6/
$ mkdir data

修改好后,我们就可以进入bin目录下启动zookeeper了

1
2
$ cd /home/hewentian/ProjectD/zookeeper-3.4.6/bin
$ ./zkServer.sh start

启动后,我们可以连到zookeeper服务器,命令如下:

1
2
$ cd /home/hewentian/ProjectD/zookeeper-3.4.6/bin
$ ./zkCli.sh -server 127.0.0.1:2181

一些操作例子,如下:

From here, you can try a few simple commands to get a feel for this simple command line interface. First, start by issuing the list command, as in ls, yielding:

1
2
[zkshell: 8] ls /
[zookeeper]

Next, create a new znode by running create /zk_test my_data. This creates a new znode and associates the string “my_data” with the node. You should see:

1
2
[zkshell: 9] create /zk_test my_data
Created /zk_test

Issue another ls / command to see what the directory looks like:

1
2
[zkshell: 11] ls /
[zookeeper, zk_test]

Notice that the zk_test directory has now been created.
Next, verify that the data was associated with the znode by running the get command, as in:

1
2
3
4
5
6
7
8
9
10
11
12
13
[zkshell: 12] get /zk_test
my_data
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 5
mtime = Fri Jun 05 13:57:06 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0
dataLength = 7
numChildren = 0

We can change the data associated with zk_test by issuing the set command, as in:

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
[zkshell: 14] set /zk_test junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0
[zkshell: 15] get /zk_test
junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0

(Notice we did a get after setting the data and it did, indeed, change.

Finally, let’s delete the node by issuing:

1
2
3
4
[zkshell: 16] delete /zk_test
[zkshell: 17] ls /
[zookeeper]
[zkshell: 18]

That’s it for now. To explore more, continue with the rest of this document and see the Programmer’s Guide.

hexo 学习笔记

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

下面将详细说说安装过程,基于 Ubuntu 16.04 LTS:

1. 安装 Git

在 Ubuntu 下面安裝非常方便,可以先在命令行中輸入git,如果没显示命令,则输入如下命令安装即可:

1
$ sudo apt-get install git

2. 安装 Node.js

先测试一下Node.js是否已安装,在命令行中直接输入node可以看到提示符变成了一个向右
的箭头就表示成功了,然后按ctrl + c退出node模式,出现$符号才表示正常了

如果未安裝 node,安裝方法如下:

1
$ sudo apt-get install nodejs-legacy

3. 安装 Hexo

进入到我们博客的仓库, 输入以下命令

1
2
3
$ sudo npm install -g hexo-cli
或者
$ sudo npm install hexo --save # 或者去掉 sudo

敲完回车可能没有任何提示,请一定要耐心等待
安装成功后,可以输入以下命令测试一下Hexo是否安装成功

1
$ hexo version

如果能看到hexo的版本号信息,就表示安装成功了
接下来,进入到我们博客的仓库

然后依次输入以下命令

1
2
3
4
5
6
7
8
$ hexo init  # 注意此命令,要求当前仓库为空,可以先将仓库的文件移走,再执行此命令,然后再将文件移回来。
$ # 可以先在当前目录的父目录创建临时目录hgi
$ mkdir ../hgi
$ mv * ../hgi # 这个命令不会将 .git .gitignore这两个文件夹移走,所以,还要执行如下命令
$ mv .git .gitignore ../hgi/
$ npm install
$ hexo g
$ hexo s

这时候在浏览器输入http://localhost:4000/ 就可以看到hexo已经成功生成了博客,当然这只是我们本地可以看到的

最后将博客的仓库目录下除了node_modules之外的文件都删掉,然后将 ../hgi 中的除了node_modules之外的文件都复制回来(不要忘记.git .gitignore),大功告成。
重启hexo

配置Hexo到Github

找到我们刚刚创建的文件夹,在里面找到_config.yml文件,用notepad++打开,直接拖到最后,添加如下:

deploy:
  type: git
  repository: git@github.com:hewentian/hewentian.github.io.git
  branch: master

发布到 github

1
2
3
$ hexo clean
$ hexo g
$ hexo d

如果出现以下异常

ERROR Deployer not found: git

尝试输入以下命令,然后重新执行刚刚的两条命令

1
$ npm install hexo-deployer-git --save

这时候我们就可以在浏览器输入 https://hewentian.github.io 就可以看到博客已经搭建成功了。

使用以下命令验证是否安装成功

1
2
3
$ node -v
$ npm -v
$ hexo -v

解释一下:
node_modules:是依赖包
public:存放的是生成的页面
scaffolds:命令生成文章等的模板
source:用命令创建的各种文章
themes:主题
_config.yml:整个博客的配置
db.json:source解析所得到的
package.json:项目所需模块项目的配置信息

添加博文

1
hexo new "postName"  #新建博文,其中postName是博文题目

博文会自动生成在博客目录下source/_posts/postName.md

注意事项

所有键的冒号后面留一个空格,如 language: zh-CN

一些常用命令:
hexo new “postName” #新建文章
hexo new page “pageName” #新建页面
hexo generate #生成静态页面至public目录, hexo g
hexo server #开启预览访问端口(默认端口4000,’ctrl + c’关闭server), hexo s
hexo deploy #将.deploy目录部署到GitHub, hexo d
hexo help # 查看帮助
hexo version #查看Hexo的版本

ENOSPC Error (Linux)报错的处理

如果启动的时候报如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ hexo s
INFO Start processing
FATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
Error: watch /home/hewentian/ProjectD/gitHub/hewentian.github.io/themes/landscape/ ENOSPC
at _errnoException (util.js:1022:11)
at FSWatcher.start (fs.js:1382:19)
at Object.fs.watch (fs.js:1408:11)
at createFsWatchInstance (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:37:15)
at setFsWatchListener (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:80:15)
at FSWatcher.NodeFsHandler._watchWithNodeFs (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:228:14)
at FSWatcher.NodeFsHandler._handleDir (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:407:19)
at FSWatcher.<anonymous> (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:455:19)
at FSWatcher.<anonymous> (/home/hewentian/ProjectD/gitHub/hewentian.github.io/node_modules/chokidar/lib/nodefs-handler.js:460:16)
at FSReqWrap.oncomplete (fs.js:153:5)

可以尝试通过如下方式解决:

1
2
$ npm dedupe
$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

java 学习笔记

想在eclipse中新建类的时候,自动在类的头部插入作者、时间等信息

方法是Window -> Preferences -> Java -> Code Style -> Code Templates在右则民展开Code -> New Java filesEdit...并加入如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
${filecomment}
${package_declaration}
/**
*
* <p>
* <b>${file_name}</b> 是
* </p>
*
* @author <a href="mailto:wentian.he@qq.com">hewentian</a>
* @date ${currentDate:date('yyyy-MM-dd')} ${time}
* @since JDK 1.8
*
*/
${typecomment}
${type_declaration}

而在IDEA中的操作方法是:
File -> Settings -> File and Code Templates,在右则选中Class,然后填入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
/**
*
* <p>
* <b>${NAME}</b> 是
* </p>
*
* @author <a href="mailto:wentian.he@qq.com">hewentian</a>
* @date ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE}:${SECOND}
* @since JDK 1.8
*
*/
public class ${NAME} {
}

Remote System Explorer Operation总是运行后台服务,卡死eclipse解决办法

eclipse后台进程在远程操作,右下角显示的“Remote System Explorer Operation”。折腾了半天,在Stack Overflow找到答案 源地址。解决方案如下:

step1: Eclipse -> Preferences -> General -> Startup and Shutdown.
    -Uncheck RSE UI.

step2: Eclipse -> Preferences -> Remote Systems.
    -Uncheck Re-open Remote Systems view to previous state.

Update your Eclipse to 4.3.1 (at least) due to a bug on previous version.

Restart Eclipse and its done.

  1. 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
  2. synchronized:可以在何意对象及方法上加锁,而加锁的这段段码称为“互斥区”或“临界区”。

当多个线程访问某个synchronized修饰的方法时,以排队的方式进行处理(这里的排除是按照CPU分配时间片的先后顺序而定的),一个线程想要执行这个方法时,首先是尝试获得锁,如果拿到锁,就执行;否则,就会不断的尝试去获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。在static方法上加上synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)

一个类中有两个方法都有synchronized修饰的话,多线程分别访问这两个方法都会进行等待,因为它们都共用同一个锁。如果其中一个没有synchronized修饰的话,多线程就可以同时访问,而不需等待。

Integer.valueOf产生的死锁

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
static class SyncAddRunnable implements Runnable {
int a;
int b;

public SyncAddRunnable(int a, int b) {
super();
this.a = a;
this.b = b;
}

public void run() {
synchronized (Integer.valueOf(a)) {
synchronized (Integer.valueOf(b)) {
System.out.println(a + b);
}
}
}
}

public static void main(String[] args) throws InterruptedException, IOException {
for (int i = 0; i < 100; i++) {
new Thread(new SyncAddRunnable(1, 2)).start();
new Thread(new SyncAddRunnable(2, 1)).start();
}
}

上面的程序会造成死锁,其原因是Integer.valueOf方法基于减少对象创建次数和节省内存的考虑,[-128, 127]之间的数字会被缓存(默认值,实际值取决于java.lang.Integer.IntegerCache.high参数的设置),当valueOf()方法传入的参数在这个范围之内,将直接返回缓存中的对象,也就是说,代码中调用了200次Integer.valueOf方法一共就只返回了2个不同的对象。假如在某个线程的两个synchronized块之间发生了线程切换,那就会出现线程A等待被线程B持有的Integer.valueOf(1),而线程B又等待着线程A持有的Integer.valueOf(2),结果就出现了死锁了。

只保留3位小数

1
2
3
java.text.NumberFormat numberFormat = java.text.NumberFormat.getNumberInstance();
numberFormat.setMaximumFractionDigits(3);
System.out.println(numberFormat.format(3.1415927d)); // 3.142

数字转成百分比

1
2
3
4
5
6
7
java.text.NumberFormat percentFormat = java.text.NumberFormat.getPercentInstance();
percentFormat.setMaximumFractionDigits(2); // 最大小数位数
percentFormat.setMinimumFractionDigits(2); // 最小小数位数
percentFormat.setMaximumIntegerDigits(2); // 最大整数位数,多了会截掉最前面的
percentFormat.setMinimumIntegerDigits(2); // 最小整数位数,不够会在最前面补0
System.out.println(percentFormat.format(3.1415927d)); // 14.16%
System.out.println(percentFormat.format(0.0415927d)); // 04.16%

btrace的使用

要使用btrace,必须先到这里下载:
https://github.com/btraceio/btrace
下载之后,解压到指定目录,还要配置系统的环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ pwd
/home/hewentian/Downloads

$ tar xf btrace-bin-1.3.11.tar.gz
$ mv btrace-bin-1.3.11 /usr/local/btrace-bin-1.3.11/

# 修改环境变量
$ vi /etc/profile

# 在/etc/profile中增加以下内容
# add btrace
export BTRACE_HOME=/usr/local/btrace-bin-1.3.11
export PATH=$BTRACE_HOME/bin:$PATH

$ source /etc/profile

下面用一个例子,演示如何使用:
示例1:获取方法参数以及返回值
将下面的代码保存为BTraceTest.java

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
package com.hewentian;

/**
*
* <p>
* <b>BTtraceTest.java</b> 是
* </p>
*
* @author <a href="mailto:wentian.he@qq.com">hewentian</a>
* @date 2018-04-19 3:57:22 PM
* @since JDK 1.8
*
*/
public class BTraceTest {
public static String whoAreYou(String name, int age) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}

return "I'm " + name + ", " + age;
}

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
String sayYou = whoAreYou("Lily", 23);
System.out.println(sayYou);
}
}
}

下面的是测试代码,同样是JAVA代码,将其保存为Btrace.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
*
* <p>
* <b>Btrace.java</b> 是
* </p>
*
* @author <a href="mailto:wentian.he@qq.com">hewentian</a>
* @date 2018-04-19 4:02:37 PM
* @since JDK 1.8
*
*/
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;

@BTrace
public class Btrace {
@OnMethod(clazz = "com.hewentian.BTraceTest", method = "whoAreYou", location = @Location(Kind.RETURN))
public static void whoAreYou(String name, int age, @Return String result) {
println("name: " + name);
println("age: " + age);
println(result);
}
}

下面开始测试:
在一个console中执行如下命令,将程序启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ javac -d . BTraceTest.java	# 编译
$ java com.hewentian.BTraceTest # 运行

I'm Lily, 23
I'm Lily, 23
I'm Lily, 23
I'm Lily, 23
btrace WARNING: Invalid 'libs' configuration [null]. Path '/usr/local/btrace-bin-1.3.11/build/btrace-libs' does not exist.
I'm Lily, 23
I'm Lily, 23
I'm Lily, 23
I'm Lily, 23
I'm Lily, 23
I'm Lily, 23

在另一个console中执行如下命令,查询上面的程序的PID,以便用于监控

1
2
3
4
$ jps -v
4498 BTraceTest
4516 Jps -Denv.class.path=.:/usr/local/java/jdk1.8.0_102/lib:/usr/local/java/jdk1.8.0_102/jre/lib: -Dapplication.home=/usr/local/java/jdk1.8.0_102 -Xms8m
2614 org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar -Dosgi.requiredJavaVersion=1.8 -Xms40m -Dosgi.module.lock.timeout=10 -Xverify:none -Xmx1200m

可以看到,我们的程序BTraceTest的PID为4498,执行下面的程序,开启btrace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ btrace 4498 Btrace.java

name: Lily
age: 23
I'm Lily, 23
name: Lily
age: 23
I'm Lily, 23
name: Lily
age: 23
I'm Lily, 23
name: Lily
age: 23
I'm Lily, 23
name: Lily
age: 23
I'm Lily, 23

示例2:计算方法运行消耗的时间,这在实际中非常有用
将下面的代码保存为BtraceDuration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*
* <p>
* <b>BtraceDuration.java</b> 是
* </p>
*
* @author <a href="mailto:wentian.he@qq.com">hewentian</a>
* @date 2018-04-19 4:46:50 PM
* @since JDK 1.8
*
*/
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;

@BTrace
public class BtraceDuration {
@OnMethod(clazz = "com.hewentian.BTraceTest", method = "whoAreYou", location = @Location(Kind.RETURN))
public static void whoAreYou(@Duration long duration) {
println(strcat("duration(ms): ", str(duration / 1000000))); // duration的单位是纳秒,要除以 1,000,000 才是毫秒
}
}

同样是按示例1的流程执行即可。

1
2
3
4
5
6
7
8
9
10
11
12
$ jps
2614 org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar
5258 Jps
5243 BTraceTest

$ btrace 5243 BtraceDuration.java
duration(ms): 5000
duration(ms): 5000
duration(ms): 5000
duration(ms): 5000
duration(ms): 5000
duration(ms): 5000

btrace的介绍就到这里,这只是入门。更多内容,参考这篇文章,它写得非常好。

在我们对一个对象的方法加锁的时候,需要考虑业务的整体性,即为getter/setter方法同时加锁synchronized同步关键字,保证业务的原子性。以免出现脏读。

关键字synchronized拥有重入锁的功能,也就是说在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。

同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好

ReentrantLock可重入锁的意思是,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。

wait notfiy 方法,wait释放锁,notfiy不释放锁。用这种方式会有不实时的坏处。因为虽然A线程发出了notify,但是它不释放锁,这样B线程还需要wait直到A执行完它的时间片。可以使用CountDownLatch来实现实时。

Object类的方法有:

  1. public: notify(), notifyAll();
  2. public: wait(), wait(long),wait(long, int);
  3. public: toString(), equals(Object), hashCode(), getClass();
  4. protected: clone(), finalize();
  5. private: registerNatives().

动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是在编译期。

java性能测试工具jmeter:http://jmeter.apache.org/
和它的好搭档badboy:http://www.badboy.com.au/
jvisualvm

trim()无法去掉空格

ascii码里有两个空格(space), 一个是ascii码32,一个是ascii码160,它们的区别是:

  1. 平时我们用键盘输入的空格的ASCII值是32;
  2. 而这个ASCII值为160的空格,其实是不间断空格(non-breaking space),我们平时一定也用过很多次,就是页面上的&nbsp;所产生的空格;
  3. 不间断空格non-breaking space的缩写正是nbsp。这中空格的作用就是在页面换行时不被打断,例如:页面某一行的末尾是一个人名Zhu Xiaoli

    1. 如果使用了平常的空格,就会被页面压缩,变成下边这样
      Zhu
      Xiaoli

    2. 如果使用了160空格,就会是这样: Zhu Xiaoli

但是不间断空格有个问题,就是它无法被trim()所裁剪,也无法被正则表达式的\s所匹配,也无法被StringUtils的isBlank()所识别,也就是说,无法像裁剪寻常空格那样移除这个不间断空格。不过,我们可以利用不间断空格的Unicode编码来移除它,其编码为\u00A0

1
2
3
4
5
6
7
8
9
String s = "abc";
s = (char) 32 + s;
s = s + (char) 160;

s = s.trim();
System.out.println(s); // abc ,前面的空格去掉了,但是后面的还在

s = s.replaceAll("[\\u00A0]+", "");
System.out.println(s); // abc,这里后面的空格也去掉了

命令行下替换jar包中指定文件

  1. 列出指定文件的路径

    jar -tvf test.jar | grep config.properties
    
  2. 解压指定路径下的文件

    jar -xvf test.jar conf/config.properties
    
  3. 修改文件

  4. 更新到jar包中

    jar -uvf test.jar conf/config.properties
    

IDEA一个工作空间打开多个项目

如果,多个项目之间不是parent和module的关系,IDEA一个工作空间只能打开一个项目。这样的话,
如果有多个项目,来回切换视图让人烦不胜烦。在IDEA一个工作空间中打开多个项目的方法。

  1. 首先打开一个项目;
  2. 点击项目右边的Maven Project,并点击绿色的+号;
  3. 找到要打开的另一个项目,选中pom.xml文件,点击 [ok] 即可。

Ubuntu卸载IDEA

缓存目录:~/.cache/JetBrains
配置目录:~/.config/JetBrains
插件目录:~/.local/share/JetBrains
快速启动图标:~/.local/share/applications/jetbrains-idea.desktop

IntelliJ IDEA 版本控制不显示颜色提示的解决方法

依次点击File -> Settings -> Version Control,在右则面板中可以看到Unregistered roots,选择下面的项目,然后点击右则的+,最后点击Apply即可。

maven 学习笔记

在ubuntu上面安装 maven

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ su root
$ cd /home/hewentian/Downloads
$ tar xzvf apache-maven-3.3.9-bin.tar.gz
$ cd /usr/local
$ mv /home/hewentian/Downloads/apache-maven-3.3.9 ./

$ vi /etc/profile
# 在打开的文件中添加如下代码
# add maven
export M2_HOME=/usr/local/apache-maven-3.3.9
export PATH=$M2_HOME/bin:$PATH

$ source /etc/profile
$ mvn -version

Maven home: /usr/local/apache-maven-3.3.9
Java version: 1.8.0_102, vendor: Oracle Corporation
Java home: /usr/local/java/jdk1.8.0_102/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.10.0-28-generic", arch: "amd64", family: "unix"

maven的学习有个好网站:https://howtodoinjava.com/maven/

Maven Dependency Scopes

Maven dependency scope attribute is used to specify the visibility of a dependency, relative to the different lifecycle phases (build, test, runtime etc). Maven provides six scopes i.e. compile, provided, runtime, test, system, and import.

  1. Compile Scope
  2. Provided Scope
  3. Runtime Scope
  4. Test Scope
  5. System Scope
  6. Import Scope

详情:https://howtodoinjava.com/maven/maven-dependency-scopes/

External Dependency

Some times, you will have to refer jar files which are not in maven repository (neither local, central or remote repository). You can use these jars by placing them in project’s lib folder and configure the external dependency like this:

1
2
3
4
5
6
7
<dependency>
<groupId>extDependency</groupId>
<artifactId>extDependency</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${basedir}\war\WEB-INF\lib\extDependency.jar</systemPath>
</dependency>

  • The groupId and artifactId are both set to the name of the dependency.
  • The scope element value is set to system.
  • The systemPath element refer to the location of the JAR file.

Maven Dependency Tree

Using maven’s dependency:tree command, you can view list of all dependencies into your project – transitively. Transitive dependency means that if A depends on B and B depends on C, then A depends on both B and C.

Transitivity brings a very serious problem when different versions of the same artifacts are included by different dependencies. It may cause version mismatch issue in runtime. In this case, dependency:tree command is be very useful in dealing with conflicts of JARs.

$ mvn dependency:tree

Maven Dependency Exclusion

Apart from version mismatch issue caused with transitive dependency, there can be version mismatch between project artifacts and artifacts from the platform of deployment, such as Tomcat or another server.

To resolve such version mismatch issues, maven provides tag, in order to break the transitive dependency.

For example, when you have JUnit 4.12 in classpath and including DBUnit dependency, then you will need to remove JUnit 3.8.2 dependency. It can be done with exclusion tag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>${dbunit.version}</version>
<scope>test</scope>
<exclusions>
<!--Exclude transitive dependency to JUnit-3.8.2 -->
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>

maven常用命令

编译,编译后会生成target目录
mvn compile

清理,将编译生成的target目录删掉
mvn clean

打包,将编译生成的target目录下的class文件打成jar包
mvn package

安装到本地仓库,把打好的包放入本地仓库(~/.m2/repository)
mvn install

安装到远程仓库,把打好的包发布到远程仓库
mvn deploy

一般组合使用这些使用,如mvn clean compilemvn clean package

eclipse中,遇到Missing artifact jdk.tools:jdk.tools:jar:1.8

原因:tools.jar包是JDK的,而tools.jar并未在仓库maven repository

解决方案:在pom文件中添加如下代码即可

1
2
3
4
5
6
7
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>

在 pom.xml文件中不要写如下这样的配置:

1
2
3
4
5
6
7
8
9
10
11
12
<properties>
<java.home>/usr/local/java/jdk1.8.0_102</java.home>
</properties>
<dependencies>
<dependency>
<groupId>jre</groupId>
<artifactId>jre</artifactId>
<version>8.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
</dependencies>

去掉<java.home>/usr/local/java/jdk1.8.0_102</java.home>配置,这时java.home变量继承自eclipsejava.home配置。从pom.xml文件的Effective POM可以查看到java.home变量被替换了,eclipse的java.home路径在help->about eclipse->Installation Details->configuration页可以找到。

解决SecurityException问题

在使用maven打JAR包,在运行的时候可能会抛如下异常:

Exception in thread “main” java.lang.SecurityException: Invalid signature file digest for Manifest main attributes

原因:由于重复引用某些依赖,导致在maven打包之后生成了一些.SF等文件,运行jar时会抛出。在META-INF下会有多余的以SF、RSA结尾的文件,删除后不会出现次问题

如无法重新打包,则解决方法如下:

zip -d yourjar.jar 'META-INF/.SF' 'META-INF/.RSA' 'META-INF/*SF'

最好的方法是在pom.xml中配置打包的时候忽略这些文件:

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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>

maven跳过单元测试的方式

  1. -DskipTests: 编译测试用例类生成相应的class文件至target/test-classes下,但不执行测试用例,当然,也可以直接在POM文件中写上:

    1
    2
    3
    4
    5
    6
    7
    8
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M3</version>
    <configuration>
    <skipTests>true</skipTests>
    </configuration>
    </plugin>
  2. -Dmaven.test.skip=true: 不编译测试用例类,也不执行测试用例;

打包的时候跳过单元测试:

mvn package -Dmaven.test.skip=true

maven打包并指定main方法

pom.xml配置如下:

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
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!--指定main方法-->
<mainClass>com.hewentian.jdk.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

创建maven项目

在IDEA中可以通过File -> New -> Module,打开New Module对话框,在左则选择Maven,并在右则勾选Create from archetype

  1. 创建jar项目,则选择org.apache.maven.archetypes:maven-archetype-quickstart
  2. 创建web项目,则选择org.apache.maven.archetypes:maven-archetype-webapp

maven-shade-plugin插件

此插件的主要功能:

  1. 将依赖的jar包打包到当前jar包中,常规打包是不会将所依赖jar包打进来的;
  2. 指定jar包的默认运行方法:https://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html;
  3. 为jar包选择指定的内容:https://maven.apache.org/plugins/maven-shade-plugin/examples/includes-excludes.html

详细介绍详见:http://maven.apache.org/plugins/maven-shade-plugin/plugin-info.html

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
<project>
...
<build>
<!-- To define the plugin version in your parent POM -->
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
</plugin>
...
</plugins>
</pluginManagement>
<!-- To use the plugin goals in your POM or parent POM -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
</plugin>
...
</plugins>
</build>
...
</project>

html-css 前端相关

将图片二进制流显示在html端的img控件中,在获取验证码的时候用得比较多,方法如下

1
2
3
<img src="data:image/jpeg;base64,fejwiqpofewqlfjewqjfewopfjef...">

在 src 后面加上 data:image/jpeg;base64,二进制码.

HTTPS网页调用HTTP服务

有时候,我们可能会在HTTPS的站点内通过Javascript脚本访问HTTP服务器,这时候,网页可能会报如下错误:

1
Mixed Content: The page at 'https://www.a.com' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://api.b.com/data/user/save'. This request has been blocked; the content must be served over HTTPS.

这个时候,只要在我们的http://api.b.com站点的服务端开放一个测试接口testApi

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
import com.b.entity.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("data/user")
public class UserController {

@PostMapping(value = "save")
public Result save(String dataList, HttpServletResponse response) {
allowOrigin(response);

if (StringUtils.isBlank(dataList)) {
return Result.getFalseResult("缺少必要的参数");
}

// save handler

return Result.getSuccResult("保存成功");
}

/**
* 仅用作浏览器在https协议下自动调用一次接口,让浏览器弹出不安全提示
*
* @param response
* @return
*/
@RequestMapping("testApi")
public Result testApi(HttpServletResponse response) {
allowOrigin(response);
return Result.getSuccResult("ok");
}

public void allowOrigin(HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Access-Control-Allow-Origin", "*");
}
}

然后在前端代码中添加如下脚本:

1
2
3
4
// 仅用作浏览器在https协议下自动调用一次接口,让浏览器弹出不安全提示
$.post("http://api.b.com/data/user/testApi", {}, function(){
//
});

确保在进入目标网页的时候会加载它,在加载它的时候在浏览器的地址栏中,我们点红色的叉会弹出一个对话框,有如下文字:

1
2
3
This page is trying to load scripts from unauthenticated sources.

Load unsafe scripts

我们点击Load unsafe scripts之后,再提原先的请求就可以了。

如何刷新清除某个特定网站的缓存(Chrome浏览器)

  1. 打开一个网站,如,百度;
  2. 按F12,进入开发者模式;
  3. 右键浏览器的刷新按钮,会出现三个选项:正常重新加载,硬性重新加载,清空缓存并硬性重新加载。

正常重新加载Ctrl+R:正常重新加载。
硬性重新加载Ctrl+Shift+R:浅层次的清除历史记录,但不一定完全清除缓存(Ctrl+F5同理)。
清空缓存并硬性重新加载:可以深层次的清除所有的缓存。(建议用这个)

网页请求不带cookie问题

为什么ajax请求没有带上cookie呢?然后去查了一下资料,原来ajax默认只会带上同源的cookie。如果只是这样还没什么,
重点是localhost和本机的Ip地址(我的是192.168.1.112)不是一个域,因为我在浏览器输入网址打的是localhost:8761
(也就是说当前域是localhost:8761),但为ajax的请求地址是写192.168.1.112:8761,所以ajax请求就没带上localhost:8761
下的cookie。补充一下:当然不仅ajax,比如直接在浏览器上输入地址或者通过表单提交都是默认只带上同源的cookie。

js发送post请求下载文件

JQuery的ajax函数的返回类型只有xml、text、json、html等类型,没有“流”类型,所以我们要实现ajax下载,
不能够使用相应的ajax函数进行文件下载。但可以用js生成一个form,用这个form提交参数,并返回“流”类型
的数据。在实现过程中,页面也没有进行刷新。

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
function toExport() {
let formData = getFormData();

if (formData) {
var $iframe = $('<iframe id="down-file-iframe" />');
var $form = $('<form target="down-file-iframe" method="post" />');
$form.attr('action', 'http://study.hewentian.com/userInfo/download');

for (var key in formData) {
$form.append('<input type="hidden" name="' + key + '" value="' + formData[key] + '" />');
}

$iframe.append($form);
$(document.body).append($iframe);
$form[0].submit();
$iframe.remove();
}
}

function getFormData() {
let userName = $('#userName').val();

let startTime = $('#startTime').val();
let endTime = $('#endTime').val();

if (isNull(startTime) || isNull(endTime)) {
layer.alert("请选择日期范围");
return;
}

return {
'userName': userName,
'startTime': startTime,
'endTime': endTime
};
}

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
@RestController
@RequestMapping("/web/userInfo")
@Slf4j
public class UserInfoController {
@Autowired
private IUserInfoService userInfoService;

@PostMapping("/download")
public void download(String userName, String startTime, String endTime, HttpServletResponse response) {
XSSFWorkbook xssfWorkbook = userInfoService.getXSSFWorkbook(userName, startTime, endTime);

try {
String filename = "用户信息-" + DateFormatUtils.format(new Date(), "yyyy-MM-dd") + ".xlsx";
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));

response.setCharacterEncoding("UTF-8");
OutputStream outputStream = response.getOutputStream();

xssfWorkbook.write(outputStream);

outputStream.flush();
outputStream.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

JAVA后台接收前端参数

如果JAVA后台想以@RequestBody方式,用实体类来接收参数,那么前端就不能以form方式提交表单。
@PostMapping(“/search”)
public ResponseEntity search(@RequestBody UserDTO dto) {

此时前端必须要使用contentType: 'application/json'指定参数类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$.ajax({
url: url,
type: method,
async: async,
xhrFields: {withCredentials: true},
contentType: 'application/json',
dataType: 'json',
data: data ? method === 'GET' ? data : JSON.stringify(data) : null,
success: function (data) {
let code = data.code;
if (code === 4003){
layer.confirm(data.message, function (index) {
$.removeCookie('username');
top.location.href = LOGIN_URI;
});
} else {
layer.msg(data.message);
}
},
error: function () {
layer.msg("客户端繁忙...");
}
});

javascript中的字符串替换函数replace

字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相
匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替
换所有匹配的子串。否则,它只替换第一个匹配子串。

1
2
3
var str = "abcdbe";
var str1 = str.replace('b','B'); // aBcdbe
var str2 = str.replace(/b/g,'B'); // aBcdBe

javascript中字符串与数组的互转

1
2
3
4
5
var str = "a,b,c,d";
var strArray = str.split(","); // 字符串转数组

var str1 = strArray.join(","); // 数组转字符串,用指定的分隔符分隔
var str2 = String(strArray); // 数组转字符串,默认的分隔符为逗号

javascript函数的参数中有逗号的情况

当往函数中传递的参数中包含有逗号的时候,要用单引号将其括起来。

1
2
3
4
5
6
7
8
var username = "scott, tiger";
var age = 20;

printUserinfo("'" + username + "'", age);

function printUserinfo(username, age) {
console.log(username + ", " + age)
}

javascript将字符串转为JSON对象

JSON.parse()方法用于将一个JSON字符串转换为对象。
var str = ‘[{“name”:”scott”,”age”:20},{“name”:”tiger”,”age”:21}]’; // str是一个字符串

var jsonArray = JSON.parse(str); // 转换为json对象

for(var i = 0; i < jsonArray.length; i++) {
    var jsonObj = jsonArray[i];
    console.log(jsonObj.name + ", " + jsonObj.age); // 取json中的值
}

javascript将JSON对象转为字符串

JSON.stringify()方法用于将javaScript值转换为JSON字符串,一般用于将服务器端返回的对象转成JSON字符串。

1
2
3
4
5
6
var objArray = [{"name":"scott","age":20},{"name":"tiger","age":21}]; // objArray是一个数组对象
var jsonStr = JSON.stringify(objArray);
console.log(jsonStr);

var jsonStr2 = JSON.stringify(objArray, null, 4);
console.log(jsonStr2);

输出结果分别如下,第二个会使用4个空格格式化输出:

1
2
3
4
5
6
7
8
9
10
11
12
[{"name":"scott","age":20},{"name":"tiger","age":21}]

[
{
"name": "scott",
"age": 20
},
{
"name": "tiger",
"age": 21
}
]

正则表达式

1
2
3
4
5
6
7
8
9
10
var phoneRegExp = new RegExp('^(06|08|09)[0-9]{8}$', 'i');
var postCodeRegExp = new RegExp('^[0-9]{5}$', 'i');

console.log(phoneRegExp.test('0612345678')); // true
console.log(phoneRegExp.test('06123456789')); // false
console.log(phoneRegExp.test('0812345678')); // true

console.log(postCodeRegExp.test('12345')); // true
console.log(postCodeRegExp.test('12345a')); // false
console.log(postCodeRegExp.test('1234')); // false