springboot应用接入druid监控

1.引入druid

 <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.0</version>
 </dependency>

2.DuidConfig

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {
    private Logger logger = LoggerFactory.getLogger(DruidConfig.class);

    /**
     * Druid内置监控页面
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        logger.info("Init druid servlet configuration ");
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        servletRegistrationBean.setServlet(new StatViewServlet());
        servletRegistrationBean.addUrlMappings("/druid/*");
        Map<String, String> initParameters = Maps.newHashMap();
         //登录查看信息的账号密码.
        initParameters.put("loginUsername", "druid");
        initParameters.put("loginPassword", "123456");
         //是否能够重置数据(禁用HTML页面上的“Reset All”功能)
        initParameters.put("resetEnable", "false");
        initParameters.put("allow", "");
        servletRegistrationBean.setInitParameters(initParameters);
        return servletRegistrationBean;
    }

     /**
     * 配置URI监控
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        //添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
        //添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}

3.启动服务

访问http://localhost:8484/druid,登录。

2018年看过的书和电影

读过的书

序号 书名 作者 读完时间
1 林徽因与梁思成 【美】费慰梅 2018-01-04
2 费正清中国回忆录 [美] 费正清 2018-01-11
3 中国通史 吕思勉 2018-01-18
4 物种起源 达尔文 2018-01-23
5 昨日的世界 (奥)斯蒂芬·茨威格 2018-01-29
6 高性能MySQL 施瓦茨 2018-01-31
7 西尔斯怀孕百科 西尔斯 2018-02-04
8 Spring Cloud微服务实战 翟永超 2018-02-22
9 枪炮、病菌与钢铁 [美] 贾雷德·戴蒙德 2018-02-25
10 认识性学 [美] 威廉·L. 雅博 2018-02-25
11 Java核心技术·卷1 [美] 凯.S.霍斯特曼 2018-02-27
12 围城 钱钟书 2018-03-01
13 微服务设计 [英] Sam Newman 2018-03-02
14 手把手教你玩脱口秀 [美]格雷格•迪恩 2018-03-08
15 文明之光(第一册) 吴军 2018-03-09
16 眼与耳的盛宴 朱大可 2018-03-14
17 浮生六记 (清)沈复 2018-03-20
18 我们仨 杨绛 2018-03-21
19 中国历代政治得失 钱穆 2018-03-31
20 什么是政治哲学 [美] 列奥·施特劳斯 2018-04-05
21 第二次世界大战战史 李德•哈特 2018-04-15
22 呐喊 鲁迅 2018-04-19
23 第三种黑猩猩 贾雷德·戴蒙德 2018-04-27
24 鲁迅的最后十年 林贤治 2018-05-01
25 我也是鲁迅的遗物:朱安传 乔丽华 2018-05-05
26 乡土中国 费孝通 2018-05-08
27 分布式Java应用 林昊 2018-05-10
28 梁启超传 吳其昌 2018-05-10
29 鲁迅传 许寿裳 2018-05-13
30 忧郁的热带 [法]克洛德·列维-斯特劳斯 2018-05-29
31 送别·我在西湖出家的经过 李叔同 2018-05-30
32 老子 汤漳平,王朝华译注 2018-06-05
33 三大师 斯蒂芬·茨威格 2018-06-06
34 知识分子论 [美]爱德华·萨义德 2018-06-13
35 写作法宝 威廉•津瑟 2018-06-18
36 当代文学的概念 洪子诚 2018-06-22
37 囚徒健身 保罗·威德 2018-06-26
38 从出生到3岁 [美]伯顿·L·怀特((Burton L.White) 2018-06-30
39 计算机网络(第5版) Andrew S. Tanenbaum 2018-07-12
40 Release Michael T. Nygard 2018-07-17
41 疯癫与文明 米歇尔·福柯 2018-07-21
42 富爸爸,穷爸爸 (美)罗伯特・T・清崎 2018-08-03
43 笑场 李诞 2018-08-12
44 人类沟通的起源 [美]迈克尔•托马塞洛 2018-08-15
45 田英章系列书法字贴 田英章 2018-08-22
46 一只特立独行的猪 王小波 2018-08-24
47 田英章硬笔楷书标准教程 田英章 2018-09-05
48 黄金时代 王小波 2018-09-12
49 白银时代 王小波 2018-09-25
50 欧楷解析 田蕴章 2018-10-02
51 红拂夜奔 王小波 2018-11-25
52 鲁迅全集第一卷 鲁迅 2018-09-04
53 鲁迅全集第二卷 鲁迅 2018-09-20
54 鲁迅全集第三卷 鲁迅 2018-10-11

看过的电影

序号 电影名 时间
1 星球大战8:最后的绝地武士 2018-01-07
2 可可西里 2018-01-13
3 麦兜故事 2018-01-15
4 心迷宫 2018-01-20
5 再次出发之纽约遇见你 2018-01-20
6 飞越疯人院 2018-01-28
7 泰坦尼克号 2018-01-31
8 天空之城 2018-02-01
9 玛丽和马克思 2018-02-10
10 哈利·波特与魔法石 2018-02-11
11 控方证人 2018-02-13
12 荒蛮故事 2018-02-18
13 乱世佳人 2018-02-24
14 怦然心动 2018-02-25
15 禁闭岛 2018-02-25
16 指环王2:双塔奇兵 2018-02-27
17 上帝之城 2018-03-03
18 绝地逃亡 2018-03-04
19 你的名字。 2018-03-04
20 饮食男女 2018-03-11
21 指环王3:王者无敌 2018-03-13
22 闻香识女人 2018-03-20
23 我爱你 2018-03-24
24 机械师 2018-03-31
25 头号玩家 2018-04-01
26 暴裂无声 2018-04-05
27 大佛普拉斯 2018-04-15
28 诺曼底大风暴 2018-04-15
29 死亡诗社 2018-04-19
30 达拉斯买家俱乐部 2018-04-22
31 后来的我们 2018-04-29
32 我的个神啊 2018-05-01
33 冬荫功2:拳霸天下 2018-06-20
34 西部世界 第二季 2018-06-27
35 生门 2018-08-14
36 东方快车谋杀案 2018-09-23
37 箭士柳白猿 2018-10-03
38 山河故人 2018-10-06
39 Hello!树先生 2018-10-14

PageHelper自动增加limit分页问题

问题
使用PageHelperi分页,报错如下:
Caused by: com.alibaba.druid.sql.parser.ParserException: syntax error, error in :’limi1 1 limit ?,?’,expect LIMIT, actual LIMIT limit
原始查询sql(select * from t limit 1)后面有limit1,结果报错显示自动添加上上limit ?,?:

排查

见PageHelper 安全调用:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/blob/master/wikis/zh/HowToUse.md#3-pagehelper-安全调用

PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。

线程中start的page 不能保证线程在当前执行退出时清理完page变量

重现

XXXServiceImpl {
     XX method(Object xx, int pageNum, int pageSize) {
          PageHelper.start(pageNum,pageSize);
          if(xx!=null){
             return XX;
          }
          xxxMapper.find();
            ....
       }
 }
在执行PageHelper.start(pageNum,pageSize);方法后,参数page变量,如xx!=null,直接返回XX,则page没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,如果接下来执行其它sql,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

如果PageHelper.start(pageNum,pageSize);之后的方法加了缓存,也会有这个问题。

解决方法 

1.使用参数方式是极其安全的2.保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,必须保证分页和查询同时有效。3.调PageHelper.clearPage(); 可以手动清理 ThreadLocal 存储的分页参,这个是新版本里的方法 其中5.0之后版本注意:

1. 配置文件中PageHelper变为了PageInterceptor
<plugin interceptor=”com.github.pagehelper.PageInterceptor”>
2. 不需要<property name=”dialect” value=”mysql”/>,自动识别数据库

思域行驶一年记录

从20170701到20180703,共行驶17208km,平均油耗5.6L,加油共37次,平均每次204,共加油7548,过路费5151,违章罚款350(-_-….),保养3次共680。 优点:动力强,省油,空间够,转向手感好 缺点:高速隔音略差,蓝色易脏,内饰一般。

siyu1 siyu2siyu3siyu4

Spring基于annotation的缓存使用总结

缓存使用思路

我们使用缓存,一般的思路是这样的:一个业务查询方法,我们先去查询缓存,如果命中缓存,则直接返回结果。如果缓存没有命中,再去查询数据库,然后将结果存入缓存,下一次再执行这个方法时,如果缓存没有过期,则直接返回缓存数据。(注:这里没考虑并发情况)。示例代码如下:

  public User getUserByName(String name) {
    // 首先查询缓存
    User result = redisHelper.get(name);
    if(result !=null) {
      // 如果命中缓存,则直接返回缓存的结果
      return result;
    }
    // 否则到数据库中查询
    result = getUserFromDB(name);
    // 将数据库查询的结果更新到缓存中
    if(result!=null) {}
      redisHelper.put(name, result);
    }
    return result;
  }

这种缓存方案的劣势

  • 缓存代码和业务代码耦合度太高,不便于维护和变更,代码可读性也差

  • 这种缓存方案不支持按照某种条件的缓存,比如有某种类型的User才需要缓存

基于annotation的缓存

我们使用Spring的基于annotation的缓存技术,通过在既有代码中添加少量它定义的各种annotation,就可以达到上述效果,而且还可以使用SpEL(Spring Expression Language来定义缓存的key和各种condition,按照某种条件进行缓存。

1、@Cacheable

  // 使用了一个缓存名叫userCache
  @Cacheable(value="userCache")
  public User getUserByName(String name) {
    // 方法内部实现不考虑缓存逻辑,直接实现业务
    User user =  getUserFromDB(name);
    return user;
  }

这里用到了Spring的一个annotation,即@Cacheable(value=”userCache”),它的意思是,当调用这个方法的时候,会从一个名叫userCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则直接返回缓存中的结果。注意:这里的value=”userCache”不是指缓存中的 key,这里缓存的key 是方法的参数name"userCache"〔拼音〕是指定义这个缓存的名称。

condition :如何按照条件操作缓存

前面的缓存方法,没有任何条件,即所有对 getUserByName 方法的调用都会起动缓存效果,如果有一个需求,就是只有账号名称的长度小于等于 4 的情况下,才做缓存,如何实现?Spring提供了一个很好的方法,那就是基于 SpEL 表达式的 condition 定义,这个 condition 是 @Cacheable注解的一个属性。

    @Cacheable(value="userCache",condition="#name.length() <= 4")
    public User getUserByName(String name)...

注意其中的 condition=”#name.length() <=4”,条件表达式返回一个布尔值,当条件为 true,则进行缓存操作,否则直接调用方法执行的返回结果。

如果有多个参数,如何进行 key 的组合

这里我们需要根据name、password对User对象进行缓存,我们可以利用 SpEL 表达式对缓存 key 进行设计。

    @Cacheable(value="userCache",key="#name.concat(#password)")
    public User getUser(String name,String password)...

也可以直接用+拼接:

 @Cacheable(value = "doctor:app", key = "'listPatientTags:'+#userCode+':'+#weimaihao")
 public List<PatientTagVo> listPatientTags(Long userCode, Long weimaihao)...

2、@CacheEvict:清空缓存

使用@Cacheable,可以完成基本的缓存查询,但当User数据发生变更,那么必须要清空缓存,以保证缓存数据的可靠性。有两种情况:

    1. 清空此user对应的缓存

    1. 清空所有缓存

  public class UserService {
    @Cacheable(value="userCache")
    public User getUserByName(String name) {
      return getUserFromDB(name);
    }
    // 清空key为user.getName()的缓存
    @CacheEvict(value="userCache",key="#user.getName()")
    public void updateUser(User user) {
      ...
    }
    // 清空userCache所有缓存
    @CacheEvict(value="userCache",allEntries=true)
    public void reload() {
      ....
    }
  }

由此可见,清空缓存的方法,就是通过 @CacheEvict 来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。

注意其中@CacheEvict(value=”userCache”,key=”#user.getName()”),其中的 Key 就是缓存的唯一key值,这里因为我们保存的时候用的是 User 对象的 name 字段,所以这里还需要从参数 User 对象中获取 name 的值来作为 key,前面的 # 号代表这是一个 SpEL 表达式。

@CacheEvict 的可靠性问题

@CacheEvict有一个属性 beforeInvocation,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。

@CacheEvict(value="userCache",allEntries=true)
public void reload() {
  throw new RuntimeException();
}

注意上面的代码,我们在 reload 的时候抛出了运行期异常,这会导致清空缓存失败。如何避免这个问题呢?我们可以用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了。

@CacheEvict(value="userCache",allEntries=true,beforeInvocation=true)
public void reload() {
  throw new RuntimeException();
}

3、@CachePut :既要保证方法被调用,又希望结果被缓存

当用@Cacheable注解时,如果重复使用相同参数调用方法的时候,方法本身不会被执行,而是直接从缓存中返回,但实际需求中,有些情况下我们希望方法一定会被调用,因为其除了返回一个结果,还做了其他事情,例如记录日志,调用接口等,这个时候,我们可以用 @CachePut,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

    @Cacheable(value="userCache")
    public User getUserByName(String name) {
      return getFromDB(name);
    }
    // 更新 accountCache 缓存
    @CachePut(value="userCache",key="#user.getName()")
    public User updateUser(User user) {
      ...
    }

如上面的代码所示,我们首先用 getUserByName 方法查询,这个时候会查询数据库一次,同时结果也记录到缓存中了。然后我们调用了 updateUser 方法,这个时候会执行数据库的更新操作且记录到缓存。@CachePut 可以保证方法被执行,且结果一定会被缓存。

@Cacheable、@CachePut、@CacheEvict 总结

通过上面的例子,我们可以看到 spring cache 主要使用以下几个注解标签,即 @Cacheable、@CachePut 和 @CacheEvict,总结一下其作用和配置方法。

    1. @Cacheable :能够根据方法的请求参数对其结果进行缓存,主要参数:

    • value:缓存的名称,可以多个:如@Cacheable(value={”cache1”,”cache2”}

    • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合

    • condition:缓存的条件,可以为空,返回 true 或者 false,只有为 true 才进行缓存

    1. @CachePut :能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用。主要的参数:

    • value:同Cacheable

    • key:同Cacheable

    • condition:同Cacheable

    1. @CacheEvict :主要针对方法配置,能够根据一定的条件对缓存进行清空。主要的参数:

    • value:同Cacheable

    • key:同Cacheable

    • condition:同Cacheable

    • allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。例如: @CachEvict(value=”testcache”,allEntries=true)

    • beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)