java面试笔记

  • JSON.parse()和JSON.stringify()

1.parse 用于从一个字符串中解析出json 对象。例如

var str='{"name":"cpf","age":"23"}'

经 JSON.parse(str) 得到:

Object: age:"23"

        name:"cpf"

        proto_:Object

ps:单引号写在{}外,每个属性都必须双引号,否则会抛出异常

2.stringify用于从一个对象解析出字符串,例如

var a={a:1,b:2}

经 JSON.stringify(a)得到:

“{“a”:1,"b":2}”

 

3ar postData = $.toJSON(postdata);  //把数组转换成json字符串

 

4//解析单个的json数组

 $("#jsonArray2").click(function(){

 var data=$(".jsonArray2").html();

 alert("-----"+data);

 var dataObj=eval("("+data+")");//转换为json对象

 alert(dataObj.length);//输出root的子对象数量

  //遍历json数组

  $.each(dataObj, function(i, item) {

  alert(item.name+"-----jsonArray-------"+item.value);

  });

 });

 5///解析标准的Json串 方法一

 $("#jsonStr").click(function(){

 var json=$(".jsonText").html();

 alert("---2--"+json);

  var item = jQuery.parseJSON(json);

  alert(item.nickname);

  alert(item.ret);

  alert(item.figureurl );

 });

 

https://blog.csdn.net/dreamstar613/article/details/61913970 jQuery AJAX 方法 success()后台传来的4种数据

https://blog.csdn.net/Real_Bird/article/details/52746663 jQuery中的Ajax(全)详解参数作用

 

二、MySQL、Oracle和SQL Server的分页查询语句

  假设当前是第PageNo页,每页有PageSize条记录,现在分别用Mysql、Oracle和SQL Server分页查询student表。

1、Mysql的分页查询: 

1 SELECT

2     *

3 FROM

4     student

5 LIMIT (PageNo - 1) * PageSize ,PageSize;

理解:(Limit n,m)  =>从第n行开始取m条记录,n从0开始算。

* 代表乘号
因为你翻页的时候需要2个参数,第一个PageNo(当前页数),第二个pageSize(每页显示的记录数)。
假设你每页显示10条,第一页:0-10,第二页:10-20,第三页:20-30。
然后你在根据规律计算的出来的规律就是startIndex=(PageNo- 1) * pagesize;
第二页的开始记录是:10=(2-1)*10 ;

2、Oracel的分页查询:

 

复制代码

 1 SELECT

 2     *

 3 FROM

 4     (

 5         SELECT

 6             ROWNUM rn ,*

 7         FROM

 8             student

 9         WHERE

10             Rownum <= pageNo * pageSize

11     )

12 WHERE

13     rn > (pageNo - 1) * pageSize

复制代码

理解:假设pageNo = 1,pageSize = 10,先从student表取出行号小于等于10的记录,然后再从这些记录取出rn大于0的记录,从而达到分页目的。ROWNUM从1开始

 

3、SQL Server分页查询:

 

复制代码

 1 SELECT

 2     TOP PageSize *

 3 FROM

 4     (

 5         SELECT

 6             ROW_NUMBER () OVER (ORDER BY id ASC) RowNumber ,*

 7         FROM

 8             student

 9     ) A

10 WHERE

11     A.RowNumber > (PageNo - 1) * PageSize

复制代码

 理解:假设pageNo = 1,pageSize = 10,先按照student表的id升序排序,rownumber作为行号,然后再取出从第1行开始的10条记录。

 

  分页查询有的数据库可能有几种方式,这里写的可能也不是效率最高的查询方式,但这是我用的最顺手的分页查询,如果有兴趣也可以对其他的分页查询的方式研究一下。

利用表的覆盖索引来加速分页查询

我们都知道,利用了索引查询的语句中如果只包含了那个索引列(覆盖索引),那么这种情况会查询很快。

 

因为利用索引查找有优化算法,且数据就在查询索引上面,不用再去找相关的数据地址了,这样节省了很多时间。另外Mysql中也有相关的索引缓存,在并发高的时候利用缓存就效果更好了。

 

在我们的例子中,我们知道id字段是主键,自然就包含了默认的主键索引。现在让我们看看利用覆盖索引的查询效果如何:

 

这次我们之间查询最后一页的数据(利用覆盖索引,只包含id列),如下:

select id from product limit 866613, 20 0.2秒

相对于查询了所有列的37.44秒,提升了大概100多倍的速度

 

那么如果我们也要查询所有列,有两种方法,一种是id>=的形式,另一种就是利用join,看下实际情况:

 

SELECT * FROM product WHERE ID > =(select id from product limit 866613, 1) limit 20

查询时间为0.2秒,简直是一个质的飞跃啊,哈哈

 

另一种写法

SELECT * FROM product a JOIN (select id from product limit 866613, 20) b ON a.ID = b.id

查询时间也很短,赞!

 

其实两者用的都是一个原理嘛,所以效果也差不多

1Mysql的分页查询语句的性能分析

MySql分页sql语句,如果和MSSQL的TOP语法相比,那么MySQL的LIMIT语法要显得优雅了许多。使用它来分页是再自然不过的事情了。

最基本的分页方式:

SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT ...

在中小数据量的情况下,这样的SQL足够用了,唯一需要注意的问题就是确保使用了索引:举例来说,如果实际SQL类似下面语句,那么在category_id, id两列上建立复合索引比较好:

复制代码代码如下:


SELECT * FROM articles WHERE category_id = 123 ORDER BY id LIMIT 50, 10

 

子查询的分页方式:

随着数据量的增加,页数会越来越多,查看后几页的SQL就可能类似:

复制代码代码如下:


SELECT * FROM articles WHERE category_id = 123 ORDER BY id LIMIT 10000, 10

一言以蔽之,就是越往后分页,LIMIT语句的偏移量就会越大,速度也会明显变慢

此时,我们可以通过子查询的方式来提高分页效率,大致如下:

SELECT * FROM articles WHERE id >=

(SELECT id FROM articles WHERE category_id = 123 ORDER BY id LIMIT 10000, 1) LIMIT 10

JOIN分页方式

SELECT * FROM `content` AS t1

JOIN (SELECT id FROM `content` ORDER BY id desc LIMIT ".($page-1)*$pagesize.", 1) AS t2

WHERE t1.id <= t2.id ORDER BY t1.id desc LIMIT $pagesize;

经过我的测试,join分页和子查询分页的效率基本在一个等级上,消耗的时间也基本一致。 explain SQL语句:

id select_type table type possible_keys key key_len ref rows Extra

1 PRIMARY <derived2> system NULL NULL NULL NULL 1

1 PRIMARY t1 range PRIMARY PRIMARY 4 NULL 6264 Using where

2 DERIVED content index NULL PRIMARY 4 NULL 27085 Using index

为什么会这样呢?因为子查询是在索引上完成的,而普通的查询时在数据文件上完成的,通常来说,索引文件要比数据文件小得多,所以操作起来也会更有效率。

实际可以利用类似策略模式的方式去处理分页,比如判断如果是一百页以内,就使用最基本的分页方式,大于一百页,则使用子查询的分页方式。

一个学生分数表,用sql语句查询出各班级的前三名

昨天去一家公司面试,被这道题难住了,哎,又失去一次好的机会。

回来 之后就再想这个问题

 

表结构及数据如下:

实现的sql语句:

刚开始的实现是

select * from student a where a.id in (SELECT b.id from student b where b.classId=a.classId ORDER BY grade DESC LIMIT 0,3) ;
看起来没毛病,其实一大堆,第一 对于mysql来说,in(里面不能使用limit) 有语法错误,第二 前三名不一定就只有3位哦

尝试改:语句理解:也就是只要班级里有三个学生的分数超过这个学生,那么这个学生就不是前三名。

select * from student b 
where 
not EXISTS(select * from student c where c.classId=b.classId and b.grade < c.grade GROUP BY c.classId HAVING COUNT(*)>3 )

 

结果显示

 

 

2MySQL调优 https://blog.csdn.net/u013087513/article/details/77899412

https://blog.csdn.net/u013252072/article/details/52912385 mysql面试题

注意:int(M)中的M表示数据显示的宽度,与实际存储的长度无关。

1、也就是int(3)和int(11)能够存储的数据是一样的,都是从-2147483648到2147483647(或者0-4294967295)。

2、int(M)只有联合zerofill参数才能有意义,否则int(3)和int(11)没有任何区别。对意义:大多数应用没有意义,只是规定一些工具用来显示字符的个数;int(1)和int(20)存储和计算均一样;

MySQL优化三大方向

 优化MySQL所在服务器内核(此优化一般由运维人员完成)。

 对MySQL配置参数进行优化(my.cnf)此优化需要进行压力测试来进行参数调整。

 对SQL语句以及表优化。

MySQL参数优化 https://blog.csdn.net/u013087513/article/details/77899412

1:MySQL 默认的最大连接数为 100,可以在 mysql 客户端使用以下命令查看
mysql> show variables like 'max_connections';
2:查看当前访问Mysql的线程
mysql> show processlist;
3:设置最大连接数
mysql>set globle max_connections = 5000;
最大可设置16384,超过没用
4:查看当前被使用的connections
mysql>show globle status like 'max_user_connections'

对MySQL语句性能优化的16条经验

 为查询缓存优化查询

 EXPLAIN 我们的SELECT查询(可以查看执行的行数)

 当只要一行数据时使用LIMIT 1

 为搜索字段建立索引

 在Join表的时候使用相当类型的列,并将其索引

 千万不要 ORDER BY RAND  ()

 避免SELECT *

 永远为每张表设置一个ID

 可以使用ENUM 而不要VARCHAR

 尽可能的使用NOT NULL

 固定长度的表会更快

 垂直分割

 拆分的DELETE或INSERT语句

 越小的列会越快

 选择正确的存储引擎

 小心 "永久链接"

具体描述如下:

(一) 使用查询缓存优化查询

大多数的MySQL服务器都开启了查询缓存。这是提高性能最有效的方法之一,而且这是被MySQL引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放入一个缓存中,这样后续的相同查询就不用操作而直接访问缓存结果了。

这里最主要的问题是,对于我们程序员来说,这个事情是很容易被忽略的。因为我们某些查询语句会让MySQL不使用缓存,示例如下:

1:SELECT username FROM user WHERE    signup_date >= CURDATE()
2:SELECT username FROM user WHERE    signup_date >= '2014-06-24‘
上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。

(二) 使用EXPLAIN关键字检测查询

使用EXPLAIN关键字可以使我们知道MySQL是如何处理SQL语句的,这样可以帮助我们分析我们的查询语句或是表结构的性能瓶颈;EXPLAIN的查询结果还会告诉我们索引主键是如何被利用的,数据表是如何被被搜索或排序的....等等。语法格式是:EXPLAIN +SELECT语句;

 

 

我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。 

(三)当只要一行数据时使用LIMIT 1

加上LIMIT 1可以增加性能。MySQL数据库引擎会在查找到一条数据后停止搜索,而不是继续往后查询下一条符合条件的数据记录。

(四)为搜索字段建立索引

索引不一定就是给主键或者是唯一的字段,如果在表中,有某个字段经常用来做搜索,需要将其建立索引

索引的有关操作如下:

1.创建索引

在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。

1.1> ALTER TABLE

ALTER TABLE 用来创建普通索引、唯一索引、主键索引和全文索引

ALTER TABLE table_name ADD INDEX index_name (column_list);

ALTER TABLE table_name ADD UNIQUE (column_list);

ALTER TABLE table_name ADD PRIMARY KEY (column_list);

ALTER TABLE table_name ADD FULLTEXT (column_list);

其中table_name是要增加索引名的表名,column_list指出对哪些列列进行索引,多列时各列之间使用半角逗号隔开。索引名index_name是可选的,如果不指定索引名称,MySQL将根据第一个索引列自动指定索引名称,另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。

1.2> CREATE INDEX

CREATE INDEX可对表增加普通索引或UNIQUE索引以及全文索引,但是不可以对表增加主键索引

CREATE INDEX index_name ON table_name (column_list);

CREATE UNIQUE index_name ON table_name (column_list);

CREATE FULLTEXT index_name ON table_name (column_list);

table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名必须指定。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。

2.索引类型

普通索引INDEX:适用于name、email等一般属性

唯一索引UNIQUE:与普通索引类似,不同的是唯一索引要求索引字段值在表中是唯一的,这一点和主键索引类似,但是不同的是,唯一索引允许有空值。唯一索引一般适用于身份证号码、用户账号等不允许有重复的属性字段上。

主键索引:其实就是主键,一般在建表时就指定了,不需要额外添加。

全文检索:只适用于VARCHAR和Text类型的字段。

注意:全文索引和普通索引是有很大区别的,如果建立的是普通索引,一般会使用like进行模糊查询,只会对查询内容前一部分有效,即只对前面不使用通配符的查询有效,如果前后都有通配符,普通索引将不会起作用对于全文索引而言在查询时有自己独特的匹配方式,例如我们在对一篇文章的标题和内容进行全文索引时:

ALTER TABLE article ADD FULLTEXT ('title', 'content'); 在进行检索时就需要使用如下的语法进行检索:

SELECT * FROM article WHERE MATCH('title', 'content') AGAINST ('查询字符串');

在使用全文检索时的注意事项:

MySql自带的全文索引只能用于数据库引擎为MYISAM的数据表,如果是其他数据引擎,则全文索引不会生效。此外,MySql自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文。另外使用MySql自带的全文索引时,如果查询字符串的长度过短将无法得到期望的搜索结果。MySql全文索引所能找到的词默认最小长度为4个字符。另外,如果查询的字符串包含停止词,那么该停止词将会被忽略。

3.组合索引

组合索引又称多列索引,就是建立索引时指定多个字段属性。有点类似于字典目录,比如查询 'guo' 这个拼音的字时,首先查找g字母,然后在g的检索范围内查询第二个字母为u的列表,最后在u的范围内查找最后一个字母为o的字。比如组合索引(a,b,c),abc都是排好序的,在任意一段a的下面b都是排好序的,任何一段b下面c都是排好序的

组合索引的生效原则是  从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用;select * from mytable where a>4 and b=7 and c=9; a用到了a是范围值相当于断点   b没有使用,c没有使用

造成断点的原因:

前边的任意一个索引没有参与查询,后边的全部不生效。

前边的任意一个索引字段参与的是范围查询,后面的不会生效。

断点跟索引字字段在SQL语句中的位置前后无关,只与是否存在有关。在网上找到了很好的示例:

比如:

  1. where a=3 and b=45 and c=5 .... #这种三个索引顺序使用中间没有断点,全部发挥作用;
  2. where a=3 and c=5... #这种情况下b就是断点,a发挥了效果,c没有效果
  3. where b=3 and c=4... #这种情况下a就是断点,在a后面的索引都没有发挥作用,这种写法联合索引没有发挥任何效果;
  4. where b=45 and a=3 and c=5 .... #这个跟第一个一样,全部发挥作用,abc只要用上了就行,跟写的顺序无关

(a,b,c) 三个列上加了联合索引(是联合索引 不是在每个列上单独加索引)而是建立了a,(a,b),(a,b,c)三个索引,另外(a,b,c)多列索引和 (a,c,b)是不一样的。
具体实例可以说明:

  1. (0) select * from mytable where a=3 and b=5 and c=4;
  2. #abc三个索引都在where条件里面用到了,而且都发挥了作用
  3. (1) select * from mytable where  c=4 and b=6 and a=3;
  4. #这条语句为了说明 组合索引与在SQL中的位置先后无关,where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样
  5. (2) select * from mytable where a=3 and c=7;
  6. #a用到索引,b没有用,所以c是没有用到索引效果的
  7. (3) select * from mytable where a=3 and b>7 and c=3;
  8. #a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引
  9. (4) select * from mytable where b=3 and c=4;
  10. #因为a索引没有使用,所以这里 bc都没有用上索引效果
  11. (5) select * from mytable where a>4 and b=7 and c=9;
  12. #a用到了  b没有使用,c没有使用
  13. (6) select * from mytable where a=3 order by b;
  14. #a用到了索引,b在结果排序中也用到了索引的效果,前面说了,a下面任意一段的b是排好序的
  15. (7) select * from mytable where a=3 order by c;
  16. #a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了,使用 explain 可以看到 filesort
  17. (8) select * from mytable where b=3 order by a;
  18. #b没有用到索引,排序中a也没有发挥索引效果

注意:在查询时,MYSQL只能使用一个索引,如果建立的是多个单列的普通索引,在查询时会根据查询的索引字段,从中选择一个限制最严格的单例索引进行查询。别的索引都不会生效。

4.查看索引

mysql> show index from tblname;
mysql> show keys from tblname;

5.删除索引

删除索引的mysql格式 :DORP INDEX IndexName ON tab_name;

注意:不能使用索引的情况 

对于普通索引而言 在使用like进行通配符模糊查询时,如果首尾之间都使用了通配符,索引时无效的。

假设查询内容的关键词为'abc'

SELECT * FROM tab_name WHERE index_column LIKE  'abc%';  #索引是有效的

SELECT * FROM tab_name WHERE index_column LIKE  '%abc';  #索引是无效的

SELECT * FROM tab_name WHERE index_column LIKE  '%cba';  #索引是有效的

SELECT * FROM tab_name WHERE index_column LIKE  '%abc%';  #索引是无效的

当检索的字段内容比较大而且检索内容前后部分都不确定的情况下,可以改为全文索引,并使用特定的检索方式。

(五)在join表的时候使用相当类型的列,并将其索引

如果在程序中有很多JOIN查询,应该保证两个表中join的字段时被建立过索引的。这样MySQL颞部会启动优化JOIN的SQL语句的机制。注意:这些被用来JOIN的字段,应该是相同类型的。例如:如果要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)  

例如:

SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = “user_id”
两个 state 字段应该是被建过索引的,而且应该是相当的类型,相同的字符集。

(六)切记不要使用ORDER BY RAND()

如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序) 

(七)避免使用SELECT *

从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果我们的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。 所以,我们应该养成一个需要什么就取什么的好的习惯。
Hibernate性能方面就会差,它不用*,但它将整个表的所有字段全查出来 
优点:开发速度快

(八)永远为每张表设置一个ID主键

我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的 AUTO_INCREMENT标志。 就算是我们 users 表有一个主键叫 “email”的字段,我们也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在我们的程序中,我们应该使用表的ID来构造我们的数据结构。 而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区…… 在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。 

(九)使用ENUM而不是VARCHAR

ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。 如果我们有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,我们知道这些字段的取值是有限而且固定的,那么,我们应该使用 ENUM 而不是 VARCHAR。

(十)尽可能的不要赋值为NULL

如果不是特殊情况,尽可能的不要使用NULL。在MYSQL中对于INT类型而言,EMPTY是0,而NULL是空值。而在Oracle中 NULL和EMPTY的字符串是一样的。NULL也需要占用存储空间,并且会使我们的程序判断时更加复杂。现实情况是很复杂的,依然会有些情况下,我们需要使用NULL值。 下面摘自MySQL自己的文档: “NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.” 

(十一) 固定长度的表会更快

如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要我们包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。 固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。 并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论我们用不用,他都是要分配那么多的空间。另外在取出值的时候要使用trim去除空格 

(十二)垂直分割

“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。

(十三)拆分大的DELETE或INSERT

如果我们需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,我们需要非常小心,要避免我们的操作让我们的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。如果我们把我们的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让我们的WEB服务Crash,还可能会让我们的整台服务器马上掛了。所以在使用时使用LIMIT 控制数量操作记录的数量。

(十四)越小的列会越快  
对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把我们的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。 参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。 如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果我们不需要记录时间,使用 DATE 要比 DATETIME 好得多。 

(十五)选择正确的存储引擎

在MYSQL中有两个存储引擎MyISAM和InnoDB,每个引擎都有利有弊。

MyISAM适合于一些需要大量查询的应用,但是对于大量写操作的支持不是很好。甚至一个update语句就会进行锁表操作,这时读取这张表的所有进程都无法进行操作直至写操作完成。另外MyISAM对于SELECT  COUNT(*)这类的计算是超快无比的。InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

MyISAM是MYSQL5.5版本以前默认的存储引擎,基于传统的ISAM类型,支持B-Tree,全文检索,但是不是事务安全的,而且不支持外键。不具有原子性。支持锁表。

InnoDB是事务型引擎,支持ACID事务(实现4种事务隔离机制)、回滚、崩溃恢复能力、行锁。以及提供与Oracle一致的不加锁的读取方式。InnoDB存储它的表和索引在一个表空间中,表空间可以包含多个文件。

MyISAM和InnoDB比较,如下图所示:

 

对于Linux版本的MYSQL  配置文件在 /etc/my.cnf中

 

在5.5之后默认的存储引擎是INNODB

可以单独进行修改也可以在创建表时修改:

ALTER TABLE tab_name ENGINE INNODB;

(十六)小心永久链接

“永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。 
而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在我们决定要使用“永久链接”之前,我们需要好好地考虑一下我们的整个系统的架构。

 

C、MySQL存储过程> https://www.cnblogs.com/justdoitba/p/7620191.html

事务与存储过程

事务管理

  事务的概念

    所谓的事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组成同一个事务的操作具备同步的特点,即事务中的语句要么都执行,要么都不执行

  事务的使用

开启事务START TRANSACTION;

    执行SQL语句

    提交事务COMMIT;

    取消事务(回滚)

  事务的提交

    事务中的操作语句都需要使用COMMIT语句手动提交,只有事务提交后其中的操作才会生效

  事务的回滚

    如果不想提交当前事务,可使用ROLLBACK语句取消当前事务

ROLLBACK语句只能针对未提交的事务执行回滚操作,已提交的事务是不能回滚的

  事务的隔离级别

REPEATABLE READ(可重复读)

READ UNCOMMITTED(读未提交)

READ COMMITTED(读提交)

SERIALIZABLE(可串行化)

事务的定义特性

  原子性

      原子性是指一个事务必须被视为一个不可分割的最小工作单元,只有事务中所有的数据库操作都执行成功,才算整个事务执行成功

  一致性

      一致性是指事务将数据库从一种状态转变为下一种一致的状态。

  隔离性

      隔离性还可以称为并发控制、可串行化、锁等,当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。  

  持久性

      事务一旦提交,其所做的修改就会永久保存到数据库中,即使数据库发生故障也不应该对其有任何影响。

      事务的持久性不能做到100%的持久,只能从事务本身的角度来保证永久性,而一些外部原因导致数据库发生故障,如硬盘损坏,那么所有提交的数据可能都会丢失。

存储过程的创建

  什么是存储过程

    存储过程就是一条或多条SQL语句的集合,当对数据库进行一系列复杂操作时,存储过程可以将这些复杂操作封装成一个代码块,以便重复使用,大大减少数据库开发人员的工作量。

  创建存储过程

CREATE PROCEDURE sp_name([proc_parameter])

[characteristics…]routine_body

 

CREATE PROCEDURE:为用来创建存储过程的关键字。

sp_name:为存储过程的名称。

proc_parameter:为指定存储过程的参数列表。

characteristics:用于指定存储过程的特性。

  变量的使用

    定义

      在MySQL中,变量可以在子程序中DECLARE声明,用于保存数据处理过程中的值,这些变量的作用范围在BEGIN…END程序中。

DECLARE var_name[,varname]…date_type[DEFAULT value];

var_name:为局部变量的名称。

DEFAULT value:子句给变量提供一个默认值,该值可以被声明为一个常数或一个表达式。如果没有DEFAULT子句,变量的初始值为NULL。

    使用SET语句为变量赋值

SET var_name =

expr[,var_name = expr]…;

 

    使用SELECT…INTO为一个或多个变量赋值

SELECT col_name[…]

INTO var_name[…] table_expr;

 

  定义条件和处理程序

    定义条件是指事先定义程序执行过程中遇到的问题

DECLARE condition_name CONDITION FOR [condition_type];

// condition_type的两种形式:

[condition_type]:

SQLSTATE[VALUE] sqlstate_value|mysql_error_code

    处理程序定义了在程序执行过程中遇到问题时应当采取的处理方式,并且保证存储过程在遇到警告或错误时能继续执行处理过程使用DECLARE语句定义

DECLARE handler_type HANDLER FOR condition_value[,…] sp_statement

handler_type:

CONTINUE|EXIT|UNDO

condition_value:

|condition_name

|SQLWARNING

|NOT FOUND

|SQLEXCEPTION

|mysql_error_code

handler_type:为错误处理方式,参数取值

      有3个:CONTINUE、EXIT和UNDO。

       CONTINUE:表示遇到错误不处理,继续执行;

       EXIT:遇到错误马上退出

       UNDO:表示遇到错误后撤回之前的操作,MySQL中暂时不支持这样的操作。sp_statement:参数为程序语句段,表示在遇到定义的错误时,需要执行的存储过程。

condition_value:表示错误类型。

  光标(游标)的使用

    在编写存储过程时,查询语句可能会返回多条记录,如果数据量非常大,则需要使用光标来逐条读取查询结果集中的记录。光标是一种用于轻松处理多行数据的机制

声明

      语法:DECLARE cursor_name CURSOR FOR select_statement

      示例:DECLARE cursor_student CURSOR FOR select s_name,s_gender FROM student;

使用

      语法:OPEN cursor_name FETCH cursor_name INTO var_name[,var_name]…

      示例:FETCH cursor_student INTO s_name, s_gender;

    关闭

CLOSE cursor_name

流程控制的使用

  存储过程中的流程控制语句用于将多个SQL语句划分或组合成符合业务逻辑的代码块,MySQL中的流程控制语句有7个

1、 IF语句

    语法格式如下:

IF expr_condition THEN statement_list

[ELSEIF expr_condition THEN statement_list]

[ELSE statement_list]

END IF

2、CASE语句

    语法格式如下:

CASE case_expr

         WHEN when_value THEN statement_list

         [WHEN when_value THEN statement_list]…

         [ELSE statement_list]

END CASE

3、 LOOP语句

    语法格式如下:

[loop_label:]LOOP

statement_list

END LOOP [loop_label]

    4、LEAVE语句

    语法格式如下:

LEAVE lable

5、ITERATE语句

    语法格式如下:

ITERATE lable

    6、REPEAT语句

    语法格式如下:

[repeat_lable:] REPEAT

statement_list

UNTIL expr_condition

END REPEAT[repeat_lable]

7、WHILE语句

    语法格式如下:

[[while_lable:] WHILE expr_condition DO

Statement_list

END WHILE [while_lable]

存储过程的使用

调用存储过程

CALL sp_name([parameter[,…]])

CALL:为调用存储过程的关键字。

sp_name:为存储过程的名称。

Parameter:为存储过程的参数。

  查看存储过程

1、使用SHOW STATUS语句

CALL sp_name([parameter[,…]])

 

2、使用SHOW CREATE语句

SHOW CREATE{PROCEDURE|FUNCTION} sp_name

3、information_schema.Routines表中查看

SELECT * FROM  information_schema.Routines

WHERE ROUTINE_NAME='CountProc1'

AND ROUTINE_TYPE='PROCEDURE'\G

修改存储过程

ALTER {PROCEDURE|FUNCTION} sp_name[characteristic…]

sp_name:表示存储过程或函数的名称。characteristic:表示要修改存储过程的哪个部分, characteristic 的取值分为8部分。

删除存储过程

DROP{ PROCEDURE|FUNCTION }[IF EXISTS] sp_name

    综合案例--存储过程应用

D、MySql 存储过程实例(附完整注释)

将下面的语句复制粘贴可以一次性执行完,我已经测试过,没有问题!

MySql存储过程简单实例: 
/********************* 创建表 *****************************/

 

delimiter //

 

DROP TABLE if exists test //

 

CREATE TABLE test(

    id int(11) NULL

) //

 

 

/********************** 最简单的一个存储过程 **********************/

drop procedure if exists sp//

CREATE PROCEDURE sp() select 1 //

 

call sp()//

 

/********************* 带输入参数的存储过程 *******************/

 

drop procedure if exists sp1 //

 

create procedure sp1(in p int)

comment 'insert into a int value'

begin

/* 定义一个整形变量 */

declare v1 int;

 

/* 将输入参数的值赋给变量 */

set v1 = p;

 

/* 执行插入操作 */

insert into test(id) values(v1);

end

//

/* 调用这个存储过程 */

call sp1(1)//

/* 去数据库查看调用之后的结果 */

select * from test//

 

 

/****************** 带输出参数的存储过程 ************************/

 

drop procedure if exists sp2 //

create procedure sp2(out p int)

/*这里的DETERMINISTIC子句表示输入和输出的值都是确定的,不会再改变.我一同事说目前mysql并没有实现该功能,因此加不加都是NOT DETERMINISTIC的*/

DETERMINISTIC

begin

select max(id) into p from test;

end

//

/* 调用该存储过程,注意:输出参数必须是一个带@符号的变量 */

call sp2(@pv)//

/* 查询刚刚在存储过程中使用到的变量 */

 

select @pv//

 

 

/******************** 带输入和输出参数的存储过程 ***********************/

 

drop procedure if exists sp3 //

create procedure sp3(in p1 int , out p2 int)

begin

 

if p1 = 1 then

/* 用@符号加变量名的方式定义一个变量,与declare类似 */

set @v = 10;

else

set @v = 20;

end if;

 

/* 语句体内可以执行多条sql,但必须以分号分隔 */

 

insert into test(id) values(@v);

select max(id) into p2 from test;

 

end

//

 

/* 调用该存储过程,注意:输入参数是一个值,而输出参数则必须是一个带@符号的变量 */

call sp3(1,@ret)//

 

select @ret//

 

 

 

/***************** 既做输入又做输出参数的存储过程 ***************************************/

 

drop procedure if exists sp4 //

create procedure sp4(inout p4 int)

begin

if p4 = 4 then

set @pg = 400;

else

set @pg = 500;

end if;

 

select @pg;

 

end//

 

call sp4(@pp)//

 

/* 这里需要先设置一个已赋值的变量,然后再作为参数传入 */

set @pp = 4//

call sp4(@pp)//

 

/********************************************************/           

 

 

E、MySQL的四种事务隔离级别

mysql实现了四种隔离级别

Read Uncommitted(未提交读)

       在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(不可重复读)

       这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。(一个事物多次读取的结果可能不一样)。也叫提交读。

Repeatable Read(可重复读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时会看到同样的数据行(mysql用快照解决该问题)。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时会发现有新的“幻影” 行(mysql用间隙锁解决该问题)。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。(一个事物多次读取的结果一样)。

Serializable(可串行化) 
       这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争

Repeatable Read隔离级别衍生问题总结

幻读:一个事务在操作过程中!有别的事务对此数据集进行了修改并提交,但这些操作第一个事务读不到,等到这个事务提交的时候,便有可能引起明明插入的数据没有查询到,但却出现插入重复的错误!

不可重复读与幻读的区别:

不可重复读是能读到其它事务已经提交的数据,幻读是读不到其它事务已提交的数据!

间隙锁:间隙锁主要用来防止幻读,用在repeatable-read隔离级别下,指的是当对数据进行条件,范围检索时,对其范围内也许并存在的值进行加锁!

比如where id <8 lock in share mode,则对8以下的值都加间隙锁,

select max(id) ........lock in share mode,则会对max(id)以上的并不存在值加间隙锁!  

select * from e where id=20 lock in share mode 则只会对id=20加锁!(此时可能只是普通的共享锁了)

select * from e lock in share mode; 则对整个e加上间隙表锁!

幻读案例:有个表(id字段为唯一约束)每次插入前需查询这字段的最大值,然后再取最大值+1插入!

事务1:                                                                            事务2:

select max(id) from e;                                                      insert into e values (11)

10                                                                                   commit;

insert into e values (11)

commit;

ERROR 1062 (23000): Duplicate entry ‘11‘ for key ‘id‘

在上述事务1中明明查询最大值为10,但插入最大值+1的时候却报错!

解决方案:利用mysql间隙锁

事务1:                                                                           事务2:

select max(id) from e lock in share mode;  

(此时会对id为10以上的所有不存在的值加间隙锁)                   

10                                                                                  insert into e values (11);

insert into e values (11)                                                   commit;  此时提交会一处于等待状态,

commit;

---------------------------------------

幻读问题解决,但是上面的例子中,会导致上面的插入语句等待。

原文地址:http://www.cnblogs.com/gauze/p/6029844.html

 

描述:

事务的基本要素(ACID)

1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

 2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

 

二、事务的并发问题 

F脏读幻读不可重复读

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数

2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

 

三、MySQL事务隔离级别

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

不可重复读(read-committed)

可重复读(repeatable-read)

串行化(serializable)

mysql默认的事务隔离级别为repeatable-read

 

 

四、用例子说明各个隔离级别的情况

1、读未提交:

    (1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

 

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

 

 

    (3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据

 

    (4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据

 

     (5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别

 

 

2、读已提交

    (1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的初始值:

 

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

 

    (3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

 

    (4)客户端B的事务提交

 

    (5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题

 

 

  3、可重复读

     (1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account

 

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交

 

    (3)在客户端A执行步骤(1)的查询:

 

    (4)执行步骤(1),lilei的balance仍然是400与步骤(1)查询结果一致,没有出现不可重复读的 问题;接着执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,所以是300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,A事务中读取的是记录的快照版本,而非最新版本,B事务的更新是创建了一个新版本来更新,不同事务的读和写是分离的

 

mysql> select * from account;

+------+--------+---------+

| id   | name   | balance |

+------+--------+---------+

|    1 | lilei  |     400 |

|    2 | hanmei |   16000 |

|    3 | lucy   |    2400 |

+------+--------+---------+

3 rows in set (0.00 sec)

 

mysql> update account set balance = balance - 50 where id = 1;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

 

mysql> select * from account;

+------+--------+---------+

| id   | name   | balance |

+------+--------+---------+

|    1 | lilei  |     300 |

|    2 | hanmei |   16000 |

|    3 | lucy   |    2400 |

+------+--------+---------+

3 rows in set (0.00 sec)

 

(5) 在客户端A提交事务,查询表account的初始值

 

mysql> commit;

Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from account;

+------+--------+---------+

| id | name | balance |

+------+--------+---------+

| 1 | lilei | 300 |

| 2 | hanmei | 16000 |

| 3 | lucy | 2400 |

+------+--------+---------+

3 rows in set (0.00 sec)

 

    (6)在客户端B开启事务,新增一条数据,其中balance字段值为600,并提交

 

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

 

mysql> insert into account values(4,'lily',600);

Query OK, 1 row affected (0.00 sec)

 

mysql> commit;

Query OK, 0 rows affected (0.01 sec)

 

(7) 在客户端A计算balance之和,值为300+16000+2400=18700,没有把客户端B的值算进去,客户端A提交后再计算balance之和,居然变成了19300,这是因为把客户端B的600算进去了

,站在客户的角度,客户是看不到客户端B的,它会觉得是天下掉馅饼了,多了600块,这就是幻读,站在开发者的角度,数据的 一致性并没有破坏。但是在应用程序中,我们得代码可能会把18700提交给用户了,如果你一定要避免这情况小概率状况的发生,那么就要采取下面要介绍的事务隔离级别“串行化”

 

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)

 

 

4.串行化

    (1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:

 

mysql> set session transaction isolation level serializable;

Query OK, 0 rows affected (0.00 sec)

 

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from account;

+------+--------+---------+

| id   | name   | balance |

+------+--------+---------+

|    1 | lilei  |   10000 |

|    2 | hanmei |   10000 |

|    3 | lucy   |   10000 |

|    4 | lily   |   10000 |

+------+--------+---------+

4 rows in set (0.00 sec)

 

    (2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。

 

mysql> set session transaction isolation level serializable;

Query OK, 0 rows affected (0.00 sec)

 

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)

 

mysql> insert into account values(5,'tom',0);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

补充:

1、SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异

2、mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行

3、事务隔离级别为读提交时,写数据只会锁住相应的行

4、事务隔离级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。

5、事务隔离级别为串行化时,读写数据都会锁住整张表

6、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

7、MYSQL MVCC实现机制参考链接:https://blog.csdn.net/whoamiyang/article/details/51901888

G.jpa特点

https://www.cnblogs.com/WangJinYang/p/4257383.html

 

H、sql常用函数:

sql常用函数

https://www.cnblogs.com/Yxxxxx/p/6847504.html

----统计函数---- 
AVG --求平均值 
COUNT  --统计数目
MAX --求最大值
MIN --求最小值
SUM --求和

----算术函数----
/***三角函数***/ 
SIN(float_expression) --返回以弧度表示的角的正弦
COS(float_expression) --返回以弧度表示的角的余弦
TAN(float_expression) --返回以弧度表示的角的正切
COT(float_expression) --返回以弧度表示的角的余切
/***反三角函数***/
ASIN(float_expression) --返回正弦是FLOAT 值的以弧度表示的角 
ACOS(float_expression) --返回余弦是FLOAT 值的以弧度表示的角
ATAN(float_expression) --返回正切是FLOAT 值的以弧度表示的角
ATAN2(float_expression1,float_expression2)

----字符串函数----
ASCII()  --函数返回字符表达式最左端字符的ASCII 码值
CHAR()  --函数用于将ASCII 码转换为字符
--如果没有输入0 ~ 255 之间的ASCII 码值CHAR 函数会返回一个NULL 值
LOWER()  --函数把字符串全部转换为小写
UPPER()  --函数把字符串全部转换为大写
STR()  --函数把数值型数据转换为字符型数据
LTRIM()  --函数把字符串头部的空格去掉
RTRIM()  --函数把字符串尾部的空格去掉
LEFT(),RIGHT(),SUBSTRING() --函数返回部分字符串


----日期函数----
DAY()  --函数返回date_expression 中的日期值
MONTH()  --函数返回date_expression 中的月份值
YEAR()  --函数返回date_expression 中的年份值
DATEADD(<datepart> ,<number> ,<date>) 
--函数返回指定日期date 加上指定的额外日期间隔number 产生的新日期
DATEDIFF(<datepart> ,<number> ,<date>) 
--函数返回两个指定日期在datepart 方面的不同之处

  • 1

43. having where区别

SQL——SQL语言全部关键字详解https://blog.csdn.net/quinnnorris/article/details/71056445

以前在学校里学习过SQLserver数据库,发现学习的都是皮毛,今天以正确的姿态谈一下MySQL中where和having的区别。

 

误区:不要错误的认为having和group by 必须配合使用。

下面以一个例子来具体的讲解:

1. where和having都可以使用的场景

    select goods_price,goods_name from sw_goods where goods_price > 100

  • 1

        select goods_price,goods_name from sw_goods having goods_price > 100

  • 1

解释:上面的having可以用的前提是我已经筛选出了goods_price字段,在这种情况下和where的效果是等效的,但是如果我没有select goods_price 就会报错!!因为having是从前筛选的字段再筛选,而where是从数据表中的字段直接进行的筛选的

2. 只可以用where,不可以用having的情况

    select goods_name,goods_number from sw_goods where goods_price > 100

  • 1

    select goods_name,goods_number from sw_goods having goods_price > 100 //报错!!!因为前面并没有筛选出goods_price 字段

 

以前在学校里学习过SQLserver数据库,发现学习的都是皮毛,今天以正确的姿态谈一下MySQL中where和having的区别。

 

误区:不要错误的认为having和group by 必须配合使用。

下面以一个例子来具体的讲解:

1. where和having都可以使用的场景

    select goods_price,goods_name from sw_goods where goods_price > 100

  • 1

    select goods_price,goods_name from sw_goods having goods_price > 100

  • 1

解释:上面的having可以用的前提是我已经筛选出了goods_price字段,在这种情况下和where的效果是等效的,但是如果我没有select goods_price 就会报错!!因为having是从前筛选的字段再筛选,而where是从数据表中的字段直接进行的筛选的。

2. 只可以用where,不可以用having的情况

    select goods_name,goods_number from sw_goods where goods_price > 100

  • 1

    select goods_name,goods_number from sw_goods having goods_price > 100 //报错!!!因为前面并没有筛选出goods_price 字段

  • 1

3. 只可以用having,不可以用where情况

查询每种goods_category_id商品的价格平均值,获取平均价格大于1000元的商品信息

    select goods_category_id , avg(goods_price) as ag from sw_goods group by goods_category having ag > 1000

  • 1

    select goods_category_id , avg(goods_price) as ag from sw_goods where ag>1000 group by goods_category //报错!!因为from sw_goods 这张数据表里面没有ag这个字段

  • 1

注意:where 后面要跟的是数据表里的字段,如果我把ag换成avg(goods_price)也是错误的!因为表里没有该字段。而having只是根据前面查询出来的是什么就可以后面接什么。

4.聚合函数是比较where、having 的关键。 
开门见山。where、聚合函数、having 在from后面的执行顺序:

where>聚合函数(sum,min,max,avg,count)>having

列出group by来比较二者。()因where和having 在使用group by时问的最多) 
若须引入聚合函数来对group by 结果进行过滤 则只能用having。(此处不多说,自己想 是先执行聚合函数还是先过滤 然后比对我上面列出的执行顺序 一看便知)

样例:select sum(score) from student  where sex='man' group by name having sum(score)>210

  • 1

注意事项 : 
1、where 后不能跟聚合函数,因为where执行顺序大于聚合函数。 
2、where 子句的作用是在对查询结果进行分组前,将不符合where条件的行去掉,即在分组之前过滤数据条件中不能包含聚组函数,使用where条件显示特定的行。 
3、having 子句的作用是筛选满足条件的组,即在分组之后过滤数据,条件中经常包含聚组函数,使用having 条件显示特定的组,也可以使用多个分组标准进行分组。

4、笛卡尔积 几张表关联数据量很大

通过最小粒度area_code关联

select a.statis_month,a.area_code
from tb_test_01 a
inner join tb_test_02 b
on a.area_code = b.area_code

STATIS_MONTH AREA_CODE
------------ ---------
200902 A2101
200902 A2102
这里通过最小的粒度关联,达到我们想要的结果.
总结:在实际应用过程中通过关联表的最小粒度关联,可以避免产生笛卡尔积.这里的最小粒度可以理解为表中的唯一性约束的字段值.

所以,我们在进行表连接查询的时候一般都会使用JOIN xxx ON xxx的语法,ON语句的执行是在JOIN语句之前的,也就是说两张表数据行之间进行匹配的时候,会先判断数据行是否符合ON语句后面的条件,再决定是否JOIN。

因此,有一个显而易见的SQL优化的方案是,当两张表的数据量比较大,又需要连接查询时,应该使用 FROM table1 JOIN table2 ON xxx的语法,避免使用 FROM table1,table2 WHERE xxx 的语法,因为后者会在内存中先生成一张数据量比较大的笛卡尔积表,增加了内存的开销。

 

 

  1. SQL优化

https://www.cnblogs.com/zhang-bo/p/9138151.html

sql优化的几种方法

在sql查询中为了提高查询效率,我们常常会采取一些措施对查询语句进行sql优化,下面总结的一些方法,有需要的可以参考参考。
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

5.in 和 not in 也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

6.下面的查询也将导致全表扫描:

select id from t where name like '%abc%'

7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100

应改为:

select id from t where num=100*2

8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)='abc'--name以abc开头的id

应改为:

select id from t where name like 'abc%'

9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,

否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

11.不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:

create table #t(...)

12.很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,

如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,

因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。

一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。

这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,

其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

18.避免频繁创建和删除临时表,以减少系统表资源的消耗。

19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,

以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。

在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
25.尽量避免大事务操作,提高系统并发能力。26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

 

数据库方面优化:1、数据库优化之分表,分表分为水平(按行)分表垂直(按列)分表  2、数据库优化之缓存 redis 减少数据库服务器压力,减少访问时间。3、读写分离,使用集群

 

 

三、spring事务隔离、传播机制、原理

A、Spring的事务管理有几种方式,Spring常用的实物隔离级别是哪几种:

1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。

事物有哪些特性:

  1. 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
  2. 一致性:数据不会因为事务的执行而遭到破坏。
  3. 隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰

https://yq.aliyun.com/articles/48893

https://www.cnblogs.com/icenter/p/5279728.html

 

在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,如未授权读取、授权读取、可重复读取和串行化。

未授权读取

  未授权读取也被成为读未提交(Read Uncommitted)该隔离级别允许脏读取,其隔离级别最低。换句话说,如果一个事务正在处理某一项数据,并对其进行了更新,但同时尚未完成事务,因此还没有进行事务提交;而与此同时,允许另一个事务也能够访问该数据。举个例子来说,事务A和事务B同时进行,事务A在整个执行阶段,会将某数据项的值从1开始,做一些列加法操作(比如说加1操作)知道变成10之后进行事务提交,此时事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2、2变成3等),而对这一些列的中间值的对去就是未授权读取。

授权读取

  授权读取也被称为已提交(Read Committed)它和未授权读取非常相近,唯一的区别就是授权读取只允许获取已经被提交的数据。同样以上面的例子来说,事务A和事务B同时进行,事务A进行与上述同样的操作,此时事务B无法看到这个数据项在事务A操作过程的所有中间值,只能看到最终的10。另外,如果说一个事务C。和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B同样可以读取到20,即授权读取允许不可重复读取

可重复读取

可重复读取(Repeatable Read),简单地说,就是保证在事务处理过程中,多次读取同一个数据时其值都和事务开始时刻是一致的因此该事物级别禁止了不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子,可重复读取隔离级别能够保证事务B在第一次事务操作过程,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一次事务操作)采用同样的查询方式,就可能会读取到10或20。

串行化

串行化(Serializable)是最严格的事务隔离级别。它要求所有事务都被串行执行即事务只能一个接一个地进行处理,不能并发执行

以上4个隔离级别的隔离性一次增强,分别解决了不同的问题,下表对这4个隔离级别进行了一个简单的对比。

隔离级别 

脏读

可重复读

幻读

未授权读取

存在

不可以

存在

授权读取

不存在

不可以

存在

可重复读取

不存在

可以

存在

串行化

不存在

可以

不存在

事务隔离级别越高,就越能保证数据的完整性和一致性,但同时对并发性能的影响也越大。通常,对于绝大多数的应用程序来说,可以优先考虑将数据库系统的隔离级别设置为授权读取,这能够在避免脏读取的同时保证较好的并发性能。尽管这种事务隔离级别会导致不可重复读、虚读和第二类丢失更新等并发问题,但较为科学的做法是在可能出现这类问题的个别场合中,由应用程序主动采用悲观锁或乐观锁来进行事务控制

所以最安全的,是Serializable,但是伴随而来也是高昂的性能开销。
另外,事务常用的两个属性:readonly和timeout
一个是设置事务为只读以提升性能。
另一个是设置事务的超时时间,一般用于防止大事务的发生。还是那句话,事务要尽可能的小!

最后引入一个问题:
一个逻辑操作需要检查的条件有20条,能否为了减小事务而将检查性的内容放到事务之外呢?

很多系统都是在DAO的内部开始启动事务,然后进行操作,最后提交或者回滚。这其中涉及到代码设计的问题。小一些的系统可以采用这种方式来做,但是在一些比较大的系统,
逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌入到DAO中,导致DAO的复用性下降。所以这不是一个好的实践。

来回答这个问题:能否为了缩小事务,而将一些业务逻辑检查放到事务外面?答案是:对于核心的业务检查逻辑,不能放到事务之外,而且必须要作为分布式下的并发控制!
一旦在事务之外做检查,那么势必会造成事务A已经检查过的数据被事务B所修改,导致事务A徒劳无功而且出现并发问题,直接导致业务控制失败。
所以,在分布式的高并发环境下,对于核心业务逻辑的检查,要采用加锁机制。
比如事务开启需要读取一条数据进行验证,然后逻辑操作中需要对这条数据进行修改,最后提交。
这样的一个过程,如果读取并验证的代码放到事务之外,那么读取的数据极有可能已经被其他的事务修改,当前事务一旦提交,又会重新覆盖掉其他事务的数据,导致数据异常。
所以在进入当前事务的时候,必须要将这条数据锁住,使用for update就是一个很好的在分布式环境下的控制手段。

一种好的实践方式是使用编程式事务而非生命式,尤其是在较为规模的项目中。对于事务的配置,在代码量非常大的情况下,将是一种折磨,而且人肉的方式,绝对不能避免这种问题。
将DAO保持针对一张表的最基本操作,然后业务逻辑的处理放入manager和service中进行,同时使用编程式事务更精确的控制事务范围。
特别注意的,对于事务内部一些可能抛出异常的情况,捕获要谨慎,不能随便的catch Exception 导致事务的异常被吃掉而不能正常回滚。

Spring配置声明式事务:

* 配置DataSource
* 配置事务管理器
* 事务的传播特性
* 那些类那些方法使用事务

Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。

    DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问 时,DataSource实际为SessionFactory,TransactionManager的实现为 HibernateTransactionManager。

根据代理机制的不同,Spring事务的配置又有几种不同的方式:

第一种方式:每个Bean都有一个代理

 第二种方式:所有Bean共享一个代理基类

第三种方式:使用拦截器

第四种方式:使用tx标签配置的拦截器

第五种方式:全注解

1、spring事务控制放在service层,在service方法中一个方法调用service中的另一个方法,默认开启几个事务

spring的事务传播方式默认是PROPAGATION_REQUIRED,判断当前是否已开启一个新事务,有则加入当前事务,否则新开一个事务(如果没有就开启一个新事务),所以答案是开启了一个事务。

2、spring 什么情况下进行事务回滚?

Spring、EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚

unchecked异常,即运行时异常runntimeException 回滚事务;

checked异常,即Exception可try{}捕获的不会回滚.当然也可配置spring参数让其回滚.

spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常).
如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。

  1. 持久性:一个事物一旦提交,它对数据库的改变就是永久的。

 

B、spring事物的传播机制

 https://www.cnblogs.com/softidea/p/5962612.html 

 https://www.cnblogs.com/icenter/p/5279728.html

看这个:https://www.cnblogs.com/softidea/p/5962612.html

Propagation

Required 需要 如果存在一个事务,则支持当前事务。如果没有事务则开启

Supports 支持 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行

Mandatory 必要的 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

required_new 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

Not_support 总是非事务地执行,并挂起任何存在的事务。

Never 绝不 总是非事务地执行,如果存在一个活动事务,则抛出异常

Nested 嵌套的 如果有就嵌套、没有就开启事务

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,
即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则(是否要有独立的事务隔离级别和锁)

 

概述

当我们调用一个基于Spring的Service接口方法(如UserService#addUser())时,它将运行于Spring管理的事务 环境中,Service接口方法可能会在内部调用其它的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况, Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法

事务传播是Spring进行事务管理的重要概念,其重要性怎么强调都不为过。但是事务传播行为也是被误解最多的地方,在本文里,我们将详细分析不同事务传播行为的表现形式,掌握它们之间的区别。

事务传播行为种类

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:

表1事务传播行为类型

传播行为

含义

PROPAGATION_REQUIRED(XML文件中为REQUIRED)

表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚

PROPAGATION_SUPPORTS(XML文件中为SUPPORTS)

表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行

PROPAGATION_MANDATORY(XML文件中为MANDATORY)

表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常

PROPAGATION_NESTED(XML文件中为NESTED)

表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同PROPAGATION_REQUIRED的一样

PROPAGATION_NEVER(XML文件中为NEVER)

表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常

PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW)

表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。

PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED)

表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行

 

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

 

Cspring aop的@Before,@Around,@After,@AfterReturn,@AfterThrowing

https://www.cnblogs.com/hq233/p/6637488.html

通知分类:
前置通知(@Before) -- 在目标方法执行前执行的通知。
后置通知(@After) -- 在目标方法执行后执行的通知。无论是否发生异常。后置通知中,无法读取目标方法返回的结果。
返回通知(@AfterReturnning) --在目标方法执行成功后执行的通知。在返回通知中可以访问目标返回结果的。 
@AfterReturnning(value="execution(* com.itnba..*(..))",returning="result")
public void afterRun(Object result){
System.out.println(result);
}

异常通知(@AfterThrowing) -- 在目标方法执行出错后执行的通知。
@AfterThrowing(value="execution(* com.itnba..*(..))",throwing="ex")
public void afterThrowing(Exception ex){
System.out.println(ex.getMessage());
}

环绕通知(@Around) -- 需要切面方法携带ProceedingJoinPoion参数,类似于一个完整的动态代理,环绕通知必须要有一个返回值,是目标方法的返 
回值。

@Around("execution(* com.itnba..*(..))")
public object aroundAdvice( ProceedingJoinPoint pjp){

object obj = null;
try{
//做前置通知
obj = pjp.proceed();
//做返回通知
}
catch(Exception ex){
//做异常通知
}
//做后置通知
return obj;
}

 

添加日志:

切面方法可以加入参数(JoinPoint) joinPost.getSignature().getXXX()获得相关方法信息
切面方法中可以加入Object参数,用来获得目标方法的返回值(只对返回通知起作用)

 

D、springMvc注解

史上最全的java spring注解,没有之一https://blog.csdn.net/achenyuan/article/details/72786759

 

@PathVariable @RequestParam区别:

/区别是@RequestParam URL传过来的值是问号“?”后跟key= value 形式,多值用&拼接。
@PathVariable 传值是地址URL地址后用斜杠“/”直接拼接值,/edit/{id}/{name}

@RequestParam
使用@RequestParam接收前段参数比较方便,前端传参的URL:
url = “${ctx}/main/mm/am/edit?Id=${Id}&name=${name}”//区别是传值是key= value 形式
后端使用集合来接受参数,灵活性较好,如果url中没有对参数赋key值,后端在接收时,会根据参数值的类型附,赋一个初始key(String、long ……)
@RequestMapping("/edit")
    public String edit(Model model, @RequestParam Map<String, Object> paramMap ) {
        long id = Long.parseLong(paramMap.get("id").toString());
        String name = paramMap.get("name").toString;
        return page("edit");
    }
@PathVariable
使用@PathVariable接收参数,参数值需要在url进行占位,前端传参的URL:
url = “${ctx}/main/mm/am/edit/${Id}/${name}”
@RequestMapping("/edit/{id}/{name}")
    public String edit(Model model, @PathVariable long id,@PathVariable String name) {
        return page("edit");

    }

前端传参的URL于后端@RequestMapping的URL必须相同且参数位置一一对应,否则前端会找不到后端地址

  1. DispatcherServlet 处理流程

https://www.cnblogs.com/tengyunhao/p/7518481.html

DispatcherServlet 继承自 HttpServlet,它遵循 Servlet 里的“init-service-destroy”三个阶段,首先我们先来看一下它的 init() 阶段。

 

四、Java中ArrayList和LinkedList区别

ArrayList和LinkedList的大致区别如下:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 

ArrayList内部是使用可増长数组实现的,所以是用get和set方法是花费数时间的,但是如果插入元素和删除元素,除非插入和删除的位置都在表末尾,否则代码开销会很大,因为里面需要数组的移动。
LinkedList是使用双链表实现的,所以get会非常消耗资源,除非位置离头部很近。但是插入和删除元素花费常数时间。

三.总结 
ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下: 
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

Atips:时间复杂度 https://www.cnblogs.com/zjss/p/5232048.html  Java中ArrayList和LinkedList区别 时间复杂度 与空间复杂度

https://blog.csdn.net/qiantujava/article/details/12898461

O(1)指的是常数时间运行,比如操作对象为一个链表,对其有一个算法,O(1)时间指的是,无论链表大或者小,所耗费的时间都是一样的

O(n)指的是某算法的运行时间与输入规模成正比,即,若输入规模为T,花费时间为N,则输入规模2T时花费时间为2N‘

 

ArrayList 是线性表(数组)
get() 直接读取第几个下标,复杂度 O(1)
add(E) 添加元素,直接在后面添加,复杂度O(1)
add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
remove()删除元素,后面的元素需要逐个移动,复杂度O(n)

LinkedList 是链表的操作
get() 获取第几个元素,依次遍历,复杂度O(n)
add(E) 添加到末尾,复杂度O(1)
add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
remove()删除元素,直接指针指向操作,复杂度O(1)

B、空间复杂度 
在LinkedList中有一个私有的内部类,定义如下:

1

2

3

4

5

private static class Entry {   

         Object element;   

         Entry next;   

         Entry previous;   

     }  

 

每个Entry对象reference列表中的一个元素,同时还有在LinkedList中它的上一个元素和下一个元素。一个有1000个元素的LinkedList对象将有1000个链接在一起的Entry对象,每个对象都对应于列表中的一个元素。这样的话,在一个LinkedList结构中将有一个很大的空间开销,因为它要存储这1000个Entity对象的相关信息。 
ArrayList使用一个内置的数组来存储元素,这个数组的起始容量是10.当数组需要增长时,新的容量按如下公式获得:新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长50%。这就意味着,如果你有一个包含大量元素的ArrayList对象,那么最终将有很大的空间会被浪费掉,这个浪费是由ArrayList的工作方式本身造成的。如果没有足够的空间来存放新的元素,数组将不得不被重新进行分配以便能够增加新的元素。对数组进行重新分配,将会导致性能急剧下降。如果我们知道一个ArrayList将会有多少个元素,我们可以通过构造方法来指定容量。我们还可以通过trimToSize方法在ArrayList分配完毕之后去掉浪费掉的空间。

 

五、String、StringBuffer与StringBuilder之间区别

1.首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

String最慢的原因:

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的但后两者的对象是变量,是可以更改的。以下面一段代码为例:

String str="abc";

System.out.println(str);

str=str+"de";

System.out.println(str);

如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢

而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

另外,有时候我们会这样对字符串进行赋值

String str="abc"+"de";

StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");

System.out.println(str);

System.out.println(stringBuilder.toString());

这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

String str="abcde";

是完全一样的,所以会很快,而如果写成下面这种形式

String str1="abc";

String str2="de";

String str=str1+str2;

那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。

2. 再来说线程安全

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder

3. 总结一下

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

 

六、乐观锁与悲观锁

首先介绍一些乐观锁与悲观锁:

  悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁

  乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

 

A乐观锁的一种实现方式- CAS(Compare and Swap 比较并交换):

  锁存在的问题:

Java在JDK1.5之前都是靠 synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。

悲观锁机制存在以下问题:  

1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。

3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

    对比于悲观锁的这些问题,另一个更加有效的锁就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。

  乐观锁:

    乐观锁( Optimistic Locking )在上文已经说过了,其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

    上面提到的乐观锁的概念中其实已经阐述了它的具体实现细节:主要就是两个步骤:冲突检测和数据更新其实现方式有一种比较典型的就是 Compare and Swap ( CAS )。

CAS:

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。   

乐观锁介绍:

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明:

如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。

下单操作包括3步骤:

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update t_goods 

set status=2,version=version+1

where id=#{id} and version=#{version};

B、乐观锁与悲观锁高并发

 

C、JAVA中线程同步的方法(7种)汇总

synchronized用在方法上锁住的是什么?

锁住的是当前对象的当前方法,会使得其他线程访问该对象的synchronized方法或者代码块阻塞,但并不会阻塞非synchronized方法。

 

synchronized(this)锁住的是什么?

锁住的是当前的对象。当synchronized块里的内容执行完之后,释放当前对象的锁。同一时刻若有多个线程访问这个对象,则会被阻塞。

 

synchronized(object)锁住的什么?

锁住的是object对象。当synchronized块里的内容执行完之后,释放object对象的锁。同一时刻若有多个线程访问这个对象,则会被阻塞。

 

这里需要注意的是如果object为Integer、String等等包装类时(new出的对象除外),并不会锁住当前对象,也不会阻塞线程。因为包装类是final的,不可修改的,如果修改则会生成一个新的对象。所以,在一个线程对其进行修改后其他线程在获取该对象的锁时,该对象已经不是原来的那个对象,所以获取到的是另一个对象的锁,所以不会产生阻塞

 

本文来自 wxgxgp 的CSDN 博客 ,全文地址请点击:

https://blog.csdn.net/wxgxgp/article/details/79931653?utm_source=copy

https://www.cnblogs.com/goody9807/p/6522176.html  

常用三种同步方式:

1、同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。.class

   /**

             * 用同步方法实现

             *

             * @param money

             */

            public synchronized void save(int money) {

                account += money;

            }

2、同步代码块

  即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    代码如: 

/**

             * 用同步代码块实现

             *

             * @param money

             */

            public void save1(int money) {

                synchronized (this) {

                    account += money;

                }

            }

同步方法:给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。

同步块:同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。

synchronized 关键字用于保护共享数据。请大家注意“共享数据

wait与notify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

3、使用特殊域变量(volatile)实现线程同步

    a.volatile关键字为域变量的访问提供了一种免锁机制

    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新

    c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 

    d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 

    

    例如: 

        在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 

    

    代码实例: 

 

 

 

        //只给出要修改的代码,其余代码与上同

        class Bank {

            //需要同步的变量加上volatile

            private volatile int account = 100;

 

            public int getAccount() {

                return account;

            }

            //这里不再需要synchronized

            public void save(int money) {

                account += money;

            }

        }

 

 

    注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 

用final域,有锁保护的域和volatile域可以避免非同步的问题。 

4、使用重入锁实现线程同步

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 

    ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

 ReenreantLock类的常用方法有:

ReentrantLock() : 创建一个ReentrantLock实例

lock() : 获得锁

unlock() : 释放锁

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 

        

    例如: 

        在上面例子的基础上,改写后的代码为: 

 

 

       //只给出要修改的代码,其余代码与上同

        class Bank {

            

            private int account = 100;

            //需要声明这个锁

            private Lock lock = new ReentrantLock();

            public int getAccount() {

                return account;

            }

            //这里不再需要synchronized

            public void save(int money) {

                lock.lock();

                try{

                    account += money;

                }finally{

                    lock.unlock();

                }

                

            }

        }

 

 

    注:关于Lock对象和synchronized关键字的选择: 

        a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 

        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 

        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

D、互斥锁

 

 

 

  1. 秒杀

https://blog.csdn.net/CSDN_Terence/article/details/77744042

 

 

七、多线程 https://blog.csdn.net/fang323619/article/details/73904351

多线程的基本概念

线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?

  • 每个进程是一个应用程序,都有独立的内存空间
  • 同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的。)

多进程有什么作用?

单进程计算机只能做一件事情。 
玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。 
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是。 
因为计算机的 CPU 只能在某个时间点上做一件事。由于计算机将在“游戏进程”和“音乐 
进程”之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时进行。 
多进程的作用不是提高执行速度,而是提高 CPU 的使用率。 
进程和进程之间的内存是独立的。

多线程有什么作用?

多线程不是为了提高执行速度,而是提高应用程序的使用率。 
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。 
可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。

什么是线程?

线程是一个进程中的执行场景。一个进程可以启动多个线程。

java 程序的运行原理?

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

线程生命周期

线程是一个进程中的执行场景,一个进程可以启动多个线程 
  
新建:采用 new 语句创建完成 
就绪:执行 start 后 
运行:占用 CPU 时间 
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合 
终止:退出 run()方法

多线程不是为了提高执行速度,而是提高应用程序的使用率.

线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.

可以给现实世界中的人类一种错觉 : 感觉多线程在同时并发执行.

 

 

八、hibernate和mybatis区别   https://blog.csdn.net/eff666/article/details/71332386

1、概述

hibernate和mybatis是当前流行的ORM框架。hibernate对数据库结构提供了较为完整的封装。mybatis主要着力点在于java对象与SQL之间的映射关系

2、Hibernate理解

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将java对象与数据库表建立映射关系,是一个全自动的orm框架。

Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用。我们从三个角度理解一下Hibernate:

(1)Hibernate是对JDBC进一步封装

原来没有使用Hiberante做持久层开发时,存在很多冗余,如:各种JDBC语句,connection的管理,所以出现了Hibernate把JDBC封装了一下,我们不用操作数据,直接操作它就行了。

(2)从分层的角度来看

我们知道非常典型的三层架构:表示层,业务层,还有持久层。Hiberante也是持久层的框架,而且持久层的框架还有很多,比如:IBatis,Nhibernate,JDO,OJB,EJB等等。

(3)Hibernate是开源的一个ORM(对象关系映射)框架

ORM,即Object-Relational Mapping,它的作用就是在关系型数据库和对象之间做了一个映射。从对象(Object)映射到关系(Relation),再从关系映射到对象。这样我们在操作数据库的时候,不需要再去和复杂SQL打交道,只要像操作对象一样操作它就可以了(把关系数据库的字段在内存中映射成对象的属性)。

简单来说,hibernate就是将对象数据保存到数据库,将数据库数据读入到对象中。

3、Hibernate的核心 

从上图中,我们可以看出Hibernate六大核心接口两个主要配置文件,以及他们直接的关系。Hibernate的所有内容都在这了。那我们从上到下简单的认识一下,每个接口进行一句话总结。

//Configuration接口:负责配置并启动Hibernate

//SessionFactory接口:负责初始化Hibernate

//Session接口:负责持久化对象的CRUD操作

//Transaction接口:负责事务

//Query接口和Criteria接口:负责执行各种数据库查询

注意:Configuration实例是一个启动期间的对象,一旦SessionFactory创建完成它就被丢弃了。

4、使用hibernate存在的原因

(1)JDBC操作数据库很繁琐 
(2)SQL语句编写并不是面向对象 
(3)可以在对象和关系表之间建立关联来简化编程 
(4)ORM简化编程 
(5)ORM跨越数据库平台

5、Hibernate的优/缺点

5.1 优点

(1)不需要编写的SQL语句(不需要编辑JDBC),只需要操作相应的对象就可以了就可以能够存储、更新、删除、加载对象,可以提高生产效

(2)使用Hibernate,移植性好(只要使用Hibernate标准开发,更换数据库时,只需要配置相应的配置文件就可以了,不需要做其它任务的操作)

(3)Hibernate实现了透明持久化。当保存一个对象时,这个对象不需要继承Hibernate中的任何类、实现任何接口,只是个纯粹的单纯对象—称为POJO对象(最纯粹的对象—这个对象没有继承第三方框架的任何类和实现它的任何接口)

(4)Hibernate是一个没有侵入性的框架,没有侵入性的框架我们一般称为轻量级框架

(5)Hibernate代码测试方便

(6)有更好的二级缓存机制,可以使用第三方缓存

5.2 缺点

(1)使用数据库特性的语句,将很难调优 
(2)对大批量数据更新存在问题 
(3)系统中存在大量的攻击查询功能 
(4)缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要你的经验和能力都很强才行。

6、MyBatis

(1)入门简单,即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。

(2)可以进行更为细致的SQL优化,可以减少查询字段

(3)缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。

(4)二级缓存机制不佳

7、Hibernate与MyBatis对比

7.1 相同点

Hibernate与MyBatis都可以是通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。

其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的。Hibernate和MyBatis都支持JDBC和JTA事务处理。

7.2 不同点

(1)hibernate是全自动,而mybatis是半自动

hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql而mybatis仅有基本的字段映射对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。

(2)hibernate数据库移植性远大于mybatis

hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。

(3)hibernate拥有完整的日志系统,mybatis则欠缺一些

hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而mybatis则除了基本记录功能外,功能薄弱很多。

(4)mybatis相比hibernate需要关心很多细节

hibernate配置要比mybatis复杂的多,学习成本也比mybatis高。但也正因为mybatis使用简单,才导致它要比hibernate关心很多技术细节。mybatis由于不用考虑很多细节,开发模式上与传统jdbc区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期bug较多,因而开发出相对稳定的软件很慢,而开发出软件却很快。hibernate则正好与之相反。但是如果使用hibernate很熟练的话,实际上开发效率丝毫不差于甚至超越mybatis。

(5)sql直接优化上,mybatis要比hibernate方便很多

由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上hibernate不及mybatis

(6)缓存机制上,hibernate要比mybatis更好一些

MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

8、总结

(1)两者相同点 
Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。

(2)两者不同点 
Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。而MyBatis在使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。)的出现会给系统的正常运行带来很大的隐患。

(3)举个形象的比喻

MyBatis:机械工具,使用方便,拿来就用,但工作还是要自己来作,不过工具是活的,怎么使由我决定。(小巧、方便、高效、简单、直接、半自动)

Hibernate:智能机器人,但研发它(学习、熟练度)的成本很高,工作都可以拜托他了,但仅限于它能做的事。 (强大、方便、高效、复杂、绕弯子、全自动)

 

九、Linux

A.Linux基本操作

描述:Linux创建子账户并设置

useradd acctname创建用户acctname
passwd acctpwd给已创建的用户acctname设置密码为acctpwd
至此,将在/home下创建acctname的目录
切换用户 su acctname

常用命令:https://blog.csdn.net/qq_34620589/article/details/70268148

https://www.cnblogs.com/cxxjohnson/p/7140505.html

B.集群部署

1.Eureka集群

 

https://blog.csdn.net/sunhuiliang85/article/details/76222517

几台Eureka之间相互注册,节点之间是平级的

3.5.1 高可用服务注册中心的概念

考虑到发生故障的情况,服务注册中心发生故障必将会造成整个系统的瘫痪,因此需要保证服务注册中心的高可用。

Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。

Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。

3.5.2 构建服务注册中心集群

Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现

 

(1)创建application-peer1.properties

     server.port=1111

eureka.instance.hostname=master

eureka.client.register-with-eureka=false

eureka.client.fetch-registry=false

eureka.instance.preferIpAddress=true

eureka.server.enableSelfPreservation=false

eureka.client.serviceUrl.defaultZone=http://backup1:1112/eureka/,http://backup2:1113/eureka/

(2)创建application-peer2.properties

server.port=1112

eureka.instance.hostname=backup1

eureka.client.register-with-eureka=false

eureka.client.fetch-registry=false

eureka.instance.preferIpAddress=true

eureka.server.enableSelfPreservation=false

eureka.client.serviceUrl.defaultZone=http://master:1111/eureka/,http://backup2:1113/eureka/

(3)创建application-peer3.properties

server.port=1113

eureka.instance.hostname=backup2

eureka.client.register-with-eureka=false

eureka.client.fetch-registry=false

eureka.instance.preferIpAddress=true

eureka.server.enableSelfPreservation=false

eureka.client.serviceUrl.defaultZone=http://master:1111/eureka/,http://backup1:1112/eureka/

(4) 在hosts文件中增加如下配置

127.0.0.1 master

127.0.0.1 backup1

127.0.0.1 backup2

3.6 失效剔除

有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使服务不能正常运作。而服务注册中心并未收到“服务下线”的请求,为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

3.7 自我保护

服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例在15分钟以之内是否低于85%,如果出现低于的情况,Eureka Server会将当前实例注册信息保护起来,让这些实例不会过期。这样做会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。

以下是自我保护相关的属性:

eureka.server.enableSelfPreservation=true. 可以设置改参数值为false,以确保注册中心将不可用的实例删除

 

十、map  hashmap  hashtable

Map的底层实现基础是我们学过的数组和链表,因为Map的数据结构问题,Map中的各个元素之间没有连接的关系,所以通过数组的方式存储Map的每个元素。

通过对存储Map 对象的数组的遍历,找到key 值符合要求的key值,然后返回 value 值。

A.Map与HashMap,Hashtable,HashSet的区别

 

最近在整理他人关于面试中,碰到的问题,这些问题基本上是不注意不深究的话,是完全不知道,因此参考他人的博客,以及自己亲自的测试,总结了这一片博客内容。

HashTable和HashMap区别

区别一:继承的父类不同 
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。

public class Hashtable<K,V> extends Dictionary<K,V>  

implements Map<K,V>, Cloneable, Serializable  

 

public class HashMap<K,V> extends AbstractMap<K,V>  

implements Map<K,V>, Cloneable, Serializable

区别二:线程安全性不同 
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。

区别三:是否提供contains方法 
HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。 
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。

**区别四:**key和value是否允许null值 
其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。 
Hashtable中,key和value都不允许出现null值。 
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。 
PS:这个面试喜欢问。

区别五:哈希值的计算方法不同,Hashtable直接使用的是对象的hashCode,而HashMap则是在对象的hashCode的基础上还进行了一些变化。

//Hashtable中可以看出的是直接采用关键字的hashcode作为哈希值

    int hash = key.hashCode();

    //然后进行模运算,求出所在哗然表的位置

    int index = (hash & 0x7FFFFFFF) % tab.length;

//HashMap中的实现

   //这两行代码的意思是先计算hashcode,然后再求其在哈希表的相应位置      

    int hash = hash(key.hashCode());

    int i = indexFor(hash, table.length);

    static int hash(int h) {

     // This function ensures that hashCodes that differ only by

     // constant multiples at each bit position have a bounded

     // number of collisions (approximately 8 at default load factor).

        h ^= (h >>> 20) ^ (h >>> 12);

        return h ^ (h >>> 7) ^ (h >>> 4);

    }

   //求位于哈希表中的位置

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

区别六:内部实现使用的数组初始化和扩容方式不同,内存初始大小不同,HashTable初始大小是11,而HashMap初始大小是16

     public Hashtable() {

                //从这里可以看出,默认的初始化大小11,

                //这里的11并不是11个字节,而是11个       Entry,这个Entry是

                //实现链表的关键结构

                //这里的0.75代表的是装载因子

                this(11, 0.75f);

     }

    public HashMap() {

        //这个默认的装载因子也是0.75

         this.loadFactor = DEFAULT_LOAD_FACTOR;

        //默认的痤为0.75*16

        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);

        //这里开始是默认的初始化大小,这里大小是16

        table = new Entry[DEFAULT_INITIAL_CAPACITY];

        init();

    }

Hashtable采用的是2*old+1,而HashMap是2*old

HashMap和HashSet的区别

HashSet实质

(1)HashSet是set的一个实现类,hashMap是Map的一个实现类,同时hashMap是hashTable的替代品(为什么后面会讲到). 
(2)HashSet以对象作为元素,而HashMap以(key-value)的一组对象作为元素,且HashSet拒绝接受重复的对象.HashMap可以看作三个视图:key的Set,value的Collection,Entry的Set。 这里HashSet就是其实就是HashMap的一个视图。 
HashSet内部就是使用Hashmap实现的,和Hashmap不同的是它不需要Key和Value两个值。 
往hashset中插入对象其实只不过是内部做了

      public boolean add(Object o) {

            return map.put(o, PRESENT)==null;

      }

参考博客: 
1. http://www.cnblogs.com/ywl925/p/3865269.html 
2. http://blog.csdn.net/kingzone_2008/article/details/8179701

 

B.HashMap碰撞问题 

HashMap是最常用的集合类框架之一,它实现了Map接口,所以存储的元素也是键值对映射的结构,并允许使用null值和null键,其内元素是无序的如果要保证有序,可以使用LinkedHashMapHashMap是线程不安全的,下篇文章会讨论。HashMap的类关系如下:

java.util 

Class HashMap<K,V>

java.lang.Object

          |--java.util.AbstractMap<K,V>

                |--java.util.HashMap<K,V>

所有已实现的接口:

Serializable,Cloneable,Map<K,V>

直接已知子类:

LinkedHashMap,PrinterStateReasons

 

HashMap中用的最多的方法就属put() 和 get() 方法;HashMap的Key值是唯一的,那如何保证唯一性呢?我们首先想到的是用equals比较,没错,这样可以实现,但随着内部元素的增多,put和get的效率将越来越低,这里的时间复杂度是O(n),假如有1000个元素,put时最差情况需要比较1000次。实际上,HashMap很少会用到equals方法,因为其内通过一个哈希表管理所有元素,哈希是通过hash单词音译过来的,也可以称为散列表,哈希算法可以快速的存取元素,当我们调用put存值时,HashMap首先会调用Key的hash方法,计算出哈希码,通过哈希码快速找到某个存放位置(桶),这个位置可以被称之为bucketIndex,但可能会存在多个元素找到了相同的bucketIndex,有个专业名词叫碰撞当碰撞发生时,这时会取到bucketIndex位置已存储的元素,最终通过equals来比较,equals方法就是碰撞时才会执行的方法,所以前面说HashMap很少会用到equals。HashMap通过hashCode和equals最终判断出Key是否已存在,如果已存在,则使用新Value值替换旧Value值,并返回旧Value值,如果不存在 ,则存放新的键值对<K, V>到bucketIndex位置。通过下面的流程图来梳理一下整个put过程。

           

 

 

最终HashMap的存储结构会有这三种情况,我们当然期望情形3是最少发生的(效率最低)。

 

HashMap 碰撞问题处理:

  碰撞:所谓“碰撞”就上面所述是多个元素计算得出相同的hashCode,在put时出现冲突。

  处理方法:

Java中HashMap是利用“拉链法”处理HashCode的碰撞问题。在调用HashMap的put方法或get方法时,都会首先调用hashcode方法,去查找相关的key,当有冲突时,再调用equals方法。hashMap基于hasing原理,我们通过put和get方法存取对象。当我们将键值对传递给put方法时,他调用键对象的hashCode()方法来计算hashCode,然后找到bucket(哈希桶)位置来存储对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。hashMap在每个链表节点存储键值对对象。当两个不同的键却有相同的hashCode时,他们会存储在同一个bucket位置的链表中。键对象的equals()来找到键值对。

 HashMap基本结构概念图:

 

 

到目前为止,我们了解了两件事:

1、HashMap通过键的hashCode来快速的存取元素。

2、当不同的对象发生碰撞时,HashMap通过单链表来解决,将新元素加入链表表头,通过next指向原有的元素。单链表在Java中的实现就是对象的引用(复合)。

 

 HashMap.put()和get()源码:

 

/**

 * Returns the value to which the specified key is mapped,

 * or if this map contains no mapping for the key.

 *

 * 获取key对应的value

 */  

public V get(Object key) {  

    if (key == null)  

        return getForNullKey();  

    //获取key的hash值  

    int hash = hash(key.hashCode());  

    // 在“该hash值对应的链表”上查找“键值等于key”的元素  

    for (Entry<K,V> e = table[indexFor(hash, table.length)];  

         e != null;  

         e = e.next) {  

        Object k;  

        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  

            return e.value;  

    }  

    return null;  

}  

 

/**

 * Offloaded version of get() to look up null keys.  Null keys map

 * to index 0.   

 * 获取key为null的键值对,HashMap将此键值对存储到table[0]的位置

 */  

private V getForNullKey() {  

    for (Entry<K,V> e = table[0]; e != null; e = e.next) {  

        if (e.key == null)  

            return e.value;  

    }  

    return null;  

}  

 

/**

 * Returns <tt>true</tt> if this map contains a mapping for the

 * specified key.

 *

 * HashMap是否包含key

 */  

public boolean containsKey(Object key) {  

    return getEntry(key) != null;  

}  

 

/**

 * Returns the entry associated with the specified key in the

 * HashMap.   

 * 返回键为key的键值对

 */  

final Entry<K,V> getEntry(Object key) {  

    //先获取哈希值。如果key为null,hash = 0;这是因为key为null的键值对存储在table[0]的位置。  

    int hash = (key == null) ? 0 : hash(key.hashCode());  

    //在该哈希值对应的链表上查找键值与key相等的元素。  

    for (Entry<K,V> e = table[indexFor(hash, table.length)];  

         e != null;  

         e = e.next) {  

        Object k;  

        if (e.hash == hash &&  

            ((k = e.key) == key || (key != null && key.equals(k))))  

            return e;  

    }  

    return null;  

}  

 

 

/**

 * Associates the specified value with the specified key in this map.

 * If the map previously contained a mapping for the key, the old

 * value is replaced.

 *

 * 将“key-value”添加到HashMap中,如果hashMap中包含了key,那么原来的值将会被新值取代

 */  

public V put(K key, V value) {  

    //如果key是null,那么调用putForNullKey(),将该键值对添加到table[0]中  

    if (key == null)  

        return putForNullKey(value);  

    //如果key不为null,则计算key的哈希值,然后将其添加到哈希值对应的链表中  

    int hash = hash(key.hashCode());  

    int i = indexFor(hash, table.length);  

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  

        Object k;  

        //如果这个key对应的键值对已经存在,就用新的value代替老的value。  

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  

            V oldValue = e.value;  

            e.value = value;  

            e.recordAccess(this);  

            return oldValue;  

        }  

    }  

 

    modCount++;  

    addEntry(hash, key, value, i);  

    return null;  

}

 

从HashMap的put()和get方法实现中可以与拉链法解决hashCode冲突解决方法相互印证。并且从put方法中可以看出HashMap是使用Entry<K,V>来存储数据

C、List<Map<String,String>>操作(遍历,比较)

https://www.cnblogs.com/zcleilei/p/6247957.html

DJava中将Map转换为JSON

一个注意的地方:要选对jar包

 

 1         Map map = new HashMap();

 2         map.put("success", "true");

 3         map.put("photoList", photoList);

 4         map.put("currentUser", "zhang");

 5         

 6         //net.sf.json.JSONObject 将Map转换为JSON方法

 7         JSONObject json = JSONObject.fromObject(map);

 8 

 9         //org.json.JSONObject 将Map转换为JSON方法

10         JSONObject json =new JSONObject(map);

 

 

十一、 java基础

A.JAVA类加载机制详解

“代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是变成语言发展的一大步”,这句话出自《深入理解JAVA虚拟机》一书,后面关于jvm的系列文章主要都是参考这本书。

JAVA源码编译由三个过程组成:

1、源码编译机制。

2、类加载机制

3、类执行机制

我们这里主要介绍编译和类加载这两种机制。

一、源码编译

代码编译由JAVA源码编译器来完成。主要是将源码编译成字节码文件(class文件)。字节码文件格式主要分为两部分:常量池和方法字节码。

二、类加载

类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束。过程共有七个阶段,其中到初始化之前的都是属于类加载的部分

加载----验证----准备----解析-----初始化----使用-----卸载

系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类,当运行某个java程序时,会启动一个java虚拟机进程,两次运行的java程序处于两个不同的JVM进程中,两个jvm之间并不会共享数据。

1、加载阶段

这个流程中的加载是类加载机制中的一个阶段,这两个概念不要混淆,这个阶段需要完成的事情有:

1)通过一个类的全限定名来获取定义此类的二进制字节流

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3)在java堆中生成一个代表这个类的Class对象作为访问方法区中这些数据的入口

由于第一点没有指明从哪里获取以及怎样获取类的二进制字节流,所以这一块区域留给我开发者很大的发挥空间。这个我在后面的类加载器中在进行介绍。

2、准备阶段

这个阶段正式为类变量(被static修饰的变量)分配内存并设置类变量初始值这个内存分配是发生在方法区中

1、注意这里并没有对实例变量进行内存分配,实例变量将会在对象实例化时随着对象一起分配在JAVA堆中。

2、这里设置的初始值,通常是指数据类型的零值

private static int a = 3;

 这个类变量a在准备阶段后的值是0,将3赋值给变量a是发生在初始化阶段

3、初始化阶段

初始化是类加载机制的最后一步这个时候才正真开始执行类中定义的JAVA程序代码。在前面准备阶段,类变量已经赋过一次系统要求的初始值,在初始化阶段最重要的事情就是对类变量进行初始化,关注的重点是父子类之间各类资源初始化的顺序。

java类中对类变量指定初始值有两种方式:1、声明类变量时指定初始值;2、使用静态初始化块为类变量指定初始值

初始化的时机

1)创建类实例的时候,分别有:1、使用new关键字创建实例;2、通过反射创建实例;3、通过反序列化方式创建实例。

new Test();

Class.forName(“com.mengdd.Test”);

2)调用某个类的类方法(静态方法)

Test.doSomething();

3)访问某个类或接口的类变量,或为该类变量赋值。  

int b=Test.a;

Test.a=b;

4)初始化某个类的子类。D

5)直接使用java.exe命令来运行某个主类。

除了上面几种方式会自动初始化一个类,其他访问类的方式都称不会触发类的初始化,称为被动引用。

1、子类引用父类的静态变量,不会导致子类初始化。

 

public class SupClass

{

    public static int a = 123;

    

    static

    {

        System.out.println("supclass init");

    }

}

 

public class SubClass extends SupClass

{

    static

    {

        System.out.println("subclass init");

    }

}

 

public class Test

{

    public static void main(String[] args)

    {

        System.out.println(SubClass.a);

    }

}

 

执行结果:

supclass init

123

2、通过数组定义引用类,不会触发此类的初始化

 

public class SupClass

{

    public static int a = 123;

    

    static

    {

        System.out.println("supclass init");

    }

}

 

public class Test

{

    public static void main(String[] args)

    {

        SupClass[] spc = new SupClass[10];

    }

}

 

执行结果:

 

3、引用常量时,不会触发该类的初始化

 

public class ConstClass

{

    public static final String A=  "MIGU";

    

    static

    {

        System.out.println("ConstCLass init");

    }

}

 

public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(ConstClass.A);

    }

}

 

执行结果:

MIGU

用final修饰某个类变量时,它的值在编译时就已经确定好放入常量池了,所以在访问该类变量时,等于直接从常量池中获取,并没有初始化该类。

初始化的步骤

1、如果该类还没有加载和连接,则程序先加载该类并连接。

2、如果该类的直接父类没有加载,则先初始化其直接父类。

3、如果类中有初始化语句,则系统依次执行这些初始化语句。

在第二个步骤中,如果直接父类又有直接父类,则系统会再次重复这三个步骤来初始化这个父类,依次类推,JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及所有的父类都会被初始化。

 

  1. 缓存  https://blog.csdn.net/cl534854121/article/details/76121182/

 

IO流总结

1.缓存穿透,缓存击穿,缓存雪崩解决方案分析

缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞

解决方案

有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DBDB瞬时压力过重雪崩。

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

1.使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间,所以这里给出两种版本代码参考:

  1. //2.6.1前单机版本锁
  2. String get(String key) {  
  3.    String value = redis.get(key);  
  4.    if (value  == null) {  
  5.     if (redis.setnx(key_mutex, "1")) {  
  6.         // 3 min timeout to avoid mutex holder crash  
  7.         redis.expire(key_mutex, 3 * 60)  
  8.         value = db.get(key);  
  9.         redis.set(key, value);  
  10.         redis.delete(key_mutex);  
  11.     } else {  
  12.         //其他线程休息50毫秒后重试  
  13.         Thread.sleep(50);  
  14.         get(key);  
  15.     }  
  16.   }  
  17. }

最新版本代码:

  1. public String get(key) {
  2.       String value = redis.get(key);
  3.       if (value == null) { //代表缓存值过期
  4.           //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
  5.   if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
  6.                value = db.get(key);
  7.                       redis.set(key, value, expire_secs);
  8.                       redis.del(key_mutex);
  9.               } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
  10.                       sleep(50);
  11.                       get(key);  //重试
  12.               }
  13.           } else {
  14.               return value;      
  15.           }
  16.  }

memcache代码:

  1. if (memcache.get(key) == null) {  
  2.     // 3 min timeout to avoid mutex holder crash  
  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
  4.         value = db.get(key);  
  5.         memcache.set(key, value);  
  6.         memcache.delete(key_mutex);  
  7.     } else {  
  8.         sleep(50);  
  9.         retry();  
  10.     }  
  11. }

2. "提前"使用互斥锁(mutex key):

在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下:

  1. v = memcache.get(key);  
  2. if (v == null) {  
  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
  4.         value = db.get(key);  
  5.         memcache.set(key, value);  
  6.         memcache.delete(key_mutex);  
  7.     } else {  
  8.         sleep(50);  
  9.         retry();  
  10.     }  
  11. } else {  
  12.     if (v.timeout <= now()) {  
  13.         if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
  14.             // extend the timeout for other threads  
  15.             v.timeout += 3 * 60 * 1000;  
  16.             memcache.set(key, v, KEY_TIMEOUT * 2);  
  17.   
  18.             // load the latest value from db  
  19.             v = db.get(key);  
  20.             v.timeout = KEY_TIMEOUT;  
  21.             memcache.set(key, value, KEY_TIMEOUT * 2);  
  22.             memcache.delete(key_mutex);  
  23.         } else {  
  24.             sleep(50);  
  25.             retry();  
  26.         }  
  27.     }  
  28. }

3. "永远不过期":  

这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

        从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

  1. String get(final String key) {  
  2.         V v = redis.get(key);  
  3.         String value = v.getValue();  
  4.         long timeout = v.getTimeout();  
  5.         if (v.timeout <= System.currentTimeMillis()) {  
  6.             // 异步更新后台异常执行  
  7.             threadPool.execute(new Runnable() {  
  8.                 public void run() {  
  9.                     String keyMutex = "mutex:" + key;  
  10.                     if (redis.setnx(keyMutex, "1")) {  
  11.                         // 3 min timeout to avoid mutex holder crash  
  12.                         redis.expire(keyMutex, 3 * 60);  
  13.                         String dbValue = db.get(key);  
  14.                         redis.set(key, dbValue);  
  15.                         redis.delete(keyMutex);  
  16.                     }  
  17.                 }  
  18.             });  
  19.         }  
  20.         return value;  
  21. }

4. 资源保护:

采用netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。

四种解决方案:没有最佳只有最合适

解决方案

优点

缺点

简单分布式互斥锁(mutex key)

 1. 思路简单

2. 保证一致性

1. 代码复杂度增大

2. 存在死锁的风险

3. 存在线程池阻塞的风险

“提前”使用互斥锁

 1. 保证一致性

同上 

不过期(本文)

1. 异步构建缓存,不会阻塞线程池

1. 不保证一致性。

2. 代码复杂度增大(每个value都要维护一个timekey)。

3. 占用一定的内存空间(每个value都要维护一个timekey)。

资源隔离组件hystrix(本文)

1. hystrix技术成熟,有效保证后端。

2. hystrix监控强大。

 

 

1. 部分访问存在降级策略。

 

 

四种方案来源网络,详文请链接:

http://carlosfu.iteye.com/blog/2269687?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

总结

针对业务系统,永远都是具体情况具体分析,没有最好,只有最合适。

最后,对于缓存系统常见的缓存满了和数据丢失问题,需要根据具体业务分析,通常我们采用LRU策略处理溢出,Redis的RDB和AOF持久化策略来保证一定情况下的数据安全。

C.gc回收过程,执行顺序

无用的对象设置成null,此时虚拟机不会立即gc,只有在内存不足时候才会触发gc,设置null的目的只是为了让gc collector省去判定该对象是否还活着的时间而已。目前虚拟机在编译优化阶段越来越智能,源码经过编译后很有可能null语句被优化掉,所以实际意义不大

String s = "Test"; s = null;//这只是标明GC可以去清理了,至于什么时候会去清这是GC的事

代码:

  1. package com.ray.ch05;
  2.  
  3. public class Test {
  4. public static void main(String[] args) throws InterruptedException {
  5. Thread.sleep(15000);
  6. System.out.println("begin time:" + DateUtil.getNow());
  7. for (long i = 0; i < 1999999999; i++) {
  8. Test test = new Test();
  9. test = null;
  10. // System.gc();
  11. }
  12. System.out.println("end time:" + DateUtil.getNow());
  13. }
  14. }
    1. 使用gc的时候test所使用的内存比较少,形成的实例非常少,但是由于经常gc,因此cup占用非常高,而且不稳定,还有gc的时间非常长,上面的数据是没有完成所有gc的数据,但是也能说明一些问题。

2、当使用=null来清空对象的时候,cup占用也高,但是比较平稳,内存的使用比gc要高大概25%,生成大量的实例,但是有一个比较明显的优势,就是执行时间短,据笔者观察,当Test的实例到达某个数量的时候,它就会突然下降,个人推测那个时候执行了gc,只是推测,没有实际根据。

总结:其实使用=null的方式并不是真的清空对象,对象只是被回收,需要等到某一个时间才清理,而gc就直接是清理,但是,使用=null的执行速度快,使用gc的执行速度慢。

D、jdk1.6、1.7、1.8区别

 

 

E、tcp/IP协议,socket

 

 

 

F、拦截器与过滤器

理解

(1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)

2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)。

G、JSON与XML的区别比较

https://www.cnblogs.com/SanMaoSpace/p/3139186.html

json与xml的优缺点比较(面试)

XML和json的优缺点
xml的优点
(1)格式统一
(2)容易与其他系统进行远程交互,数据共享比较方便
xml的缺点
(1)xml文件庞大,文件格式复杂,传输占带宽
(2)服务器和客户端都需要花费大量代码来解析xml,导致服务器和客户端代码变得异常复杂且不易维护
(3)客户端和服务端解析xml花费较多的资源和时间
json的优点
(1)数据格式比较简单,易于读写,格式是压缩的,占用带宽小
(2)易于解析,包括JavaScript可以通过简单的通过eval_r()进行json数据的读取,支持多种语言
json的缺点
(1)没有xml那么通用
(2)json格式目前还在推广阶段

 

H、解析xml的四种方式

https://blog.csdn.net/xinyuan_java/article/details/66100196

 

XML的解析方式分为四种:1、DOM解析;2、SAX解析;3、JDOM解析;4、DOM4J解析。其中前两种属于基础方法,是官方提供的平台无关的解析方式;后两种属于扩展方法,它们是在基础的方法上扩展出来的,只适用于java平台。

<?xml version="1.0" encoding="UTF-8"?>

<bookstore>

    <book id="1">

        <name>冰与火之歌</name>

        <author>乔治马丁</author>

        <year>2014</year>

        <price>89</price>

    </book>

    <book id="2">

        <name>安徒生童话</name>

        <year>2004</year>

        <price>77</price>

        <language>English</language>

    </book>    

</bookstore>

举例Dom4j解析:

public class DOM4JTest {

    private static ArrayList<Book> bookList = new ArrayList<Book>();

    /**

     * @param args

     */

    public static void main(String[] args) {

        // 解析books.xml文件

        // 创建SAXReader的对象reader

        SAXReader reader = new SAXReader();

        try {

            // 通过reader对象的read方法加载books.xml文件,获取docuemnt对象

            Document document = reader.read(new File("src/res/books.xml"));

            // 通过document对象获取根节点bookstore

            Element bookStore = document.getRootElement();

            // 通过element对象的elementIterator方法获取迭代器

            Iterator it = bookStore.elementIterator();

            // 遍历迭代器,获取根节点中的信息(书籍)

            while (it.hasNext()) {

                System.out.println("=====开始遍历某一本书=====");

                Element book = (Element) it.next();

                // 获取book的属性名以及 属性值

                List<Attribute> bookAttrs = book.attributes();

                for (Attribute attr : bookAttrs) {

                    System.out.println("属性名:" + attr.getName() + "--属性值:"

                            + attr.getValue());

                }

                Iterator itt = book.elementIterator();

                while (itt.hasNext()) {

                    Element bookChild = (Element) itt.next();

                    System.out.println("节点名:" + bookChild.getName() + "--节点值:" + bookChild.getStringValue());

                }

                System.out.println("=====结束遍历某一本书=====");

            }

        } catch (DocumentException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

}

 

I、Java中创建对象的几种方法

创建一个对象不是只有通过new操作可以完成,还可以有其他的方法,比如clone(),反射等。但是总的来说,一般归类为四种情况: 调用new语句创建对象,也是最常见的一种 运用反射手段创建对象 调用对象的clone()方法 运用序列化手段

https://blog.csdn.net/xdzhouxin/article/details/79879871

创建对象方式 https://blog.csdn.net/hustzw07/article/details/72518298

Java开发中,我们每天会创建很多对象,也会使用依赖注入的方式管理系统,比如:Spring去创建对象。然后究竟有多少种创建对象的方法呢?

这里列举一下:使用New关键字、使用Class类的newInstance方法、使用Constructor类的newInstance方法、使用Clone方法、使用反序列化。

 

1new关键字

这是最简单最常用的创建对象方式,包括无参的和有参的构造函数。

 

Student student = new Student();

Class类的newInstance方法

2、我们也可以使用Class类的newInstance方法创建对象,如:

Student student2 = (Student)Class.forName(className).newInstance();

// 或者:

Student stu = Student.class.newInstance();

这里我用的是JDK 1.8,Class类的源码如下:

public final class Class<T> implements java.io.Serializable,

                              GenericDeclaration,

                              Type,

                              AnnotatedElement {

    // other code

    public T newInstance()

        throws InstantiationException, IllegalAccessException

    {

        // ...

        // Constructor lookup

        // other code

        if (cachedConstructor == null) {

            if (this == Class.class) {

                throw new IllegalAccessException(

                    "Can not call newInstance() on the Class for java.lang.Class"

                );

            }

            try {

                // ...

                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);

                // 1. 构造函数是公有的或者方法可见

                // ...

                cachedConstructor = c;

            } catch (NoSuchMethodException e) {

                throw (InstantiationException)

                    new InstantiationException(getName()).initCause(e);

            }

        }

        Constructor<T> tmpConstructor = cachedConstructor;

        // ...

        // Run constructor

        try {

            return tmpConstructor.newInstance((Object[])null);// 2. 无参构造函数

        } catch (InvocationTargetException e) {

            // ...

        }

    }

 

    // other code

}

查看源码可知,这个newInstance方法调用无参的构造器创建对象的。

3Constructor类的newInstance方法

该方式和Class类的newInstance方法很像。java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。

Constructor<Student> constructor = Student.class.getInstance();

Student stu = constructor.newInstance();

其实这两种newInstance的方法就是我们所说的反射。查看第二部分的源码第2 条解释,不难看出,Class的newInstance方法内部是调用Constructor的newInstance方法。其实这也是众多框架Spring、Hibernate、Struts等使用后者的方式。

4Clone()

无论何时我们调用一个对象的clone方法,JVM都会创建一个新的对象,同时将前面的对象的内容全部拷贝进去(浅克隆)深克隆是copy对象的所有,在jvm虚拟机开辟一个新的空间,不同的引用地址。事实上,用clone方法创建对象并不会调用任何构造函数。需要注意的是,要使用clone方法,我们必须先实现Cloneable接口并实现其定义的clone方法。

Student stu2 = <Student>stu.clone();

原型模式就是采用这种方式。

5、反序列化

Java 中常常进行 JSON 数据跟 Java 对象之间的转换,即序列化和反序列化。

当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口,虽然该接口没有任何方法。

ObjectInputStream in = new ObjectInputStream (new FileInputStream("data.obj"));

Student stu3 = (Student)in.readObject();

值得一说的时,很多时候一些优秀的第三方库可以帮我们很容易地实现序列化和反序列化。比如Jackson 、 Gson。下面是一个Jackson 的一个例子:

    public static <T> T readValue(String content, TypeReference valueType) {

        try {

         ObjectMapper objectMapper = new ObjectMapper();

            return (T) objectMapper.readValue(content, valueType);

        } catch (Exception e) {

            throw new RuntimeException(e);

        }

    }

总的看来,除了使用new关键字之外的其他方法全部都是转变为invokevirtual(创建对象的直接方法) 创建。使用被new的方式转变为两个调用,new和invokespecial(构造函数调用)。

对比

两种newInstance方法区别:

1. 从包名看,Class类位于java的lang包中,而构造器类是java反射机制的一部分。

2. 实现上,Class类的newInstance只触发无参数的构造方法创建对象,而构造器类的newInstance能触发有参数或者任意参数的构造方法。(查看第二部分的源码第2 条解释,)

3. Class类的newInstance需要其构造方法是共有的或者对调用方法可见的,而构造器类的newInstance可以在特定环境下调用私有构造方法来创建对象。这点可以从上面源码的第1 条解释可以看出。

4. Class类的newInstance抛出类构造函数的异常,而构造器类的newInstance包装了一个InvocationTargetException异常。这是封装了一次的结果。

Class类本质上调用了反射包构造器类中无参数的newInstance方法,捕获了InvocationTargetException,将构造器本身的异常抛出。

 

有无使用构造函数:

有:

new

Class类的newInstance

Constructor类的newInstance

 

无:

反序列化

Clone

 

---------------------

 

本文来自 hustzw07 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/hustzw07/article/details/72518298?utm_source=copy

十二、分布式

ASpringBoot——自动配置依赖[spring-boot-starter-XXX]

https://blog.csdn.net/hackerHL/article/details/78274022

SpringBoot之所以流行的原因,主要就是因为自动配置依赖——【约定优先于配置】,提供日常企业应用的各种场景。有非常多的“开箱即用”的依赖模块,也是SpringBoot快速高效的原因。默认支持和提供了【80+的自动配置依赖模块】

SpringBoot提供的自动配置依赖模块都以spring-boot-starter-为命名前缀,并且这些依赖都在org.springframework.boot下。 
所有的spring-boot-starter都有约定俗成的默认配置,但允许调整这些配置调整默认的行为。 
SpringBoot默认的配置文件是application.properties

spring-boot-starter-XXX常用依赖

1、spring-boot-starter-logging 
如果在pom.xml中配置了spring-boot-starter-logging,会使用logback作为应用日志框架。 
只要将spring-boot-starter-logging作为依赖加入当前应用的classpath,无需多余的配置,“开箱即用”。 
也可以SpringBoot提供的应用日志设定作调整 
1、遵循logback约定,在classpath中做logback.xml配置 
2、或者在随意一个地方提供logback.xml配置文件,在application.properties中制定配置 

2、spring-boot-starter-log4j 
很多人习惯用spring-boot-starter-log4j 

3、spring-boot-starter-web 
加入依赖直接得到一个可执行的基于SpringMVC 的web应用。 
加入该依赖之后,可以直接开始编写Controller、Service、Reposity曾等。

spring-boot-starter-web依赖模块默认使用嵌入式tomcat作为web容器对外提供http服务,若不想使用tomcat(spring-boot-starter-tomcat是自动配置模块),可以引入spring-boot-starter-jetty等作为代替方案

spring-boot-starter-web提供了很多以server为前缀的配置项用于对嵌入式web容器提供配置。如【server.port、server.address、server.ssl.、server.tomcat.】 
SpringBoot甚至允许直接对嵌入式的web容器实例进行定制,可以通过向IOC容器中注册一个EmbeddedServletContainerCustomizer类型的组件对嵌入式web容器进行定制。

@SpringBootApplication

@EnableScheduling

/*@EnableTransactionManagement*/

public class BackStageApplication {

    public static void main(String[] args) {

        SpringApplication.run(BackStageApplication.class, args);

    }

    /**

     * 自定义常见几种状态跳转路径,对应跳转到CustomExceptionController中进行相应处理

     * @return EmbeddedServletContainerCustomizer

     */

    @Bean

    public EmbeddedServletContainerCustomizer containerCustomizer() {

        return new EmbeddedServletContainerCustomizer() {

            @Override

            public void customize(ConfigurableEmbeddedServletContainer container) {

                ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");

                ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");

                ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");

                container.addErrorPages(error401Page, error404Page, error500Page);

            }

        };

    }

}

4、spring-boot-starter-jdbc

使用该依赖,SpringBoot会自动配置访问数据的基本设施

spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/postgres

spring.datasource.username=postgres

spring.datasource.password=root

spring-boot-starter-jdbc以及相关的自动配置不是总是带来便利,在某些场景下,在一个应用中可能需要或依赖多个数据库,就会有多个datasource,配置就会出错。需要使用@Primary标志哪一个数据源是主数据源

SpringBoot还提供很多其他数据访问相关的自动配置模块,比如spring-boot-starter-data-jpaspring-boot-starter-data-mongdb,可以根据数据访问的具体场景使用这些自动配置模块

5、spring-boot-starter-aop 
java平台上的AOP方案的历史: 
代码生成–>动态代理–>字节码增强–>静态编译等… 
现在比较通用的AOP方案:SpringAOP结合AspecJ的方式,即使用SpringAOP的面向对象的方式来编写和组织切入逻辑,并使用AspectJ的Pointcut描述语言配合Annotation来标注和指明切入点。 
aop的应用场景很多,比如可以使用spring-boot-starter-aop 
打造一个支持性能监控的工具 
对于非功能性的代码,可以使用AOP的方式剥离到相应的AspectJ中单独维护。

Bspring boot与spring mvc的区别是什么?

spring boot只是一个配置工具,整合工具,辅助工具.                                                          

springmvc是框架,项目中实际运行的代码
Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的ioc和 aop. ioc 提供了依赖注入的容器, aop解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。

Spring MVC是基于Servlet 的一个 MVC 框架主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种XML、 JavaConfig、hin处理起来比较繁琐。于是为了简化开发者的使用,从而创造性地推出了Spring boot,约定优于配置,简化了spring的配置流程。

说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。大家觉得挺好用,于是按照这种模式搞了一个 MVC框架(一些用Spring 解耦的组件),用开发 web 应用( SpringMVC )。然后发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这套就是 Spring Boot。

Spring MVC的功能

Spring MVC提供了一种轻度耦合的方式来开发web应用。

Spring MVC是Spring的一个模块,式一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。

 

Spring Boot的功能

Spring Boot实现了自动配置,降低了项目搭建的复杂度。

众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。

Spring Boot只是承载者,辅助你简化项目搭建过程的。如果承载的是WEB项目,使用Spring MVC作为MVC框架,那么工作流程和你上面描述的是完全一样的,因为这部分工作是Spring MVC做的而不是Spring Boot。

对使用者来说,换用Spring Boot以后,项目初始化方法变了,配置文件变了,另外就是不需要单独安装Tomcat这类容器服务器了,maven打出jar包直接跑起来就是个网站,但你最核心的业务逻辑实现与业务流程实现没有任何变化。

所以,用最简练的语言概括就是:

Spring 是一个“引擎”;

Spring MVC 是基于Spring的一个 MVC 框架;

Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。

C、Eureka

http://www.mamicode.com/info-detail-2274622.html

Eureka还有客户端缓存功能(注:Eureka分为客户端程序与服务器端程序两个部分,客户端程序负责向外提供注册与发现服务接口)。所以即便Eureka集群中所有节点都失效,或者发生网络分割故障导致客户端不能访问任何一台Eureka服务器;Eureka服务的消费者仍然可以通过Eureka客户端缓存来获取现有的服务注册信息。甚至最极端的环境下,所有正常的Eureka节点都不对请求产生相应,也没有更好的服务器解决方案来解决这种问题
时;得益于Eureka的客户端缓存技术,消费者服务仍然可以通过Eureka客户端查询与获取注册服务信息,这点很重要。注意:新添加的服务是不能被请求到的,因为宕机后,不能存到缓存中

D、Eureka与Zookeeper服务注册中心比较

http://www.sohu.com/a/225545093_639793

服务注册中心,给客户端提供可供调用的服务列表,客户端在进行远程服务调用时,根据服务列表然后选择服务提供方的服务地址进行服务调用。服务注册中心在分布式系统中大量应用,是分布式系统中不可或缺的组件,例如rocketmq的name server,hdfs中的namenode,dubbo中的zk注册中心,spring cloud中的服务注册中心eureka。

在spring cloud中,除了可以使用eureka作为注册中心外,还可以通过配置的方式使用zookeeper作为注册中心。既然这样,我们该如何选择注册中心的实现呢?

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。在此Zookeeper保证的是CP, 而Eureka则是AP

2. Zookeeper保证CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

3. Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

qq_34802515

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值