├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── zx │ │ └── springmybatis │ │ ├── SpringMyBatisApplication.java │ │ ├── config │ │ ├── cache │ │ │ ├── CustomRedisCacheExpireProperties.java │ │ │ └── RedisCacheConfig.java │ │ ├── druid │ │ │ ├── DruidDataSourceConfiguration.java │ │ │ ├── DruidStatFilter.java │ │ │ └── DruidStatViewServlet.java │ │ └── mybatis │ │ │ └── CommonMapper.java │ │ ├── controller │ │ └── TestController.java │ │ ├── dao │ │ ├── GradeMapper.java │ │ ├── UserMapper.java │ │ └── driver │ │ │ └── CommonConditionLanguageDriver.java │ │ ├── dto │ │ └── GradeDTO.java │ │ ├── entity │ │ ├── Grade.java │ │ └── User.java │ │ └── service │ │ ├── UserService.java │ │ └── impl │ │ ├── CacheService.java │ │ └── UserServiceImpl.java └── resources │ ├── application.yml │ ├── generator │ ├── config.properties │ └── generatorConfig.xml │ ├── logback.xml │ ├── mapper │ └── User.xml │ └── sql │ └── test1.sql └── test └── java └── com └── zx └── springmybatis ├── SpringMyBatisApplicationTests.java ├── dao └── UserMapperTest.java └── service └── impl └── CacheServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### SpringBoot整合MyBatis/通用mapper/PageHelper,学习MyBatis注解/注解形式的动态sql等 2 | https://gitee.com/free/Mapper mapper主页 3 | https://gitee.com/free/Mapper/blob/master/wiki/mapper3/5.Mappers.md mapper所有方法 4 | https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md MybatisGeneator插件学习 5 | http://blog.csdn.net/gebitan505/article/details/54929287 6 | 7 | 8 | #### 记录 9 | * 在github逛到一个支付宝支付的无需申请支付宝api的项目...文档大略看了一遍就把项目撸下来了. 10 | 想看看它是如何实现..知道对方已经支付成功的...然后就看见...他妈的..对方创建订单后..通知管理员, 11 | 然后管理员打开自己的支付宝,通过比对金额和邮箱等信息,确认对方支付,手动修改状态....我的天.. 12 | 算了...也算是一个可行的个人支付方案把.. 13 | 14 | #### bug 15 | * 如果出现无法读取yml文件的错误,检查yml文件的编码,删除所有中文即可 16 | 17 | * 在aop方法等处,获取到request对象 18 | > HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); 19 | 20 | 21 | #### 奇淫巧技 22 | * 在github上随便看的xpay项目中的,比较不错的获取ip的方法. 23 | > 24 | /** 25 | * 获取客户端IP地址 26 | * @param request 请求 27 | * @return 28 | */ 29 | public static String getIpAddr(HttpServletRequest request) { 30 | String ip = request.getHeader("x-forwarded-for"); 31 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 32 | ip = request.getHeader("Proxy-Client-IP"); 33 | } 34 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 35 | ip = request.getHeader("WL-Proxy-Client-IP"); 36 | } 37 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 38 | ip = request.getRemoteAddr(); 39 | if (ip.equals("127.0.0.1")) { 40 | //根据网卡取本机配置的IP 41 | InetAddress inet = null; 42 | try { 43 | inet = InetAddress.getLocalHost(); 44 | } catch (UnknownHostException e) { 45 | e.printStackTrace(); 46 | } 47 | ip = inet.getHostAddress(); 48 | } 49 | } 50 | // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 51 | if (ip != null && ip.length() > 15) { 52 | if (ip.indexOf(",") > 0) { 53 | ip = ip.substring(0, ip.indexOf(",")); 54 | } 55 | } 56 | return ip; 57 | } 58 | > 59 | 60 | * 使用logback后,让控制台恢复彩色日志(该操作在Spring Boot官方文档中有更详细的说明) 61 | > 62 | logback.xml如下配置 63 | 64 | 65 | 66 | 67 | 68 | 70 | 71 | 73 | 74 | 75 | 76 | ${CONSOLE_LOG_PATTERN} 77 | utf8 78 | 79 | 80 | 81 | yml增加如下配置 82 | spring: 83 | output: 84 | ansi: 85 | enabled: always 86 | > 87 | 88 | * SpringBoot默认日志格式: 89 | > [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n 90 | 91 | * IDEA, ctrl + backspace,快速删除 92 | 93 | * 使用System.out.printf("cacheName:%s",item); 格式化输出.注意时后缀时tf 94 | 95 | * IDEA/Spring Boot/yml文件中的属性中,按 CTRL + B ,可进入该属性注入的代码处..屌..无意中按了下 96 | 97 | * 想到了一个lombok中@NonNull注解比较好的使用方式,只要在异常处理类中处理NullPointException,将其封装成自定义异常处理即可; 98 | 这样,使用@NonNull注解后,就可以较为优雅地处理这类算是已经自己处理的异常了 99 | 100 | * Guava CaseFormat:驼峰命名转换工具类 101 | 102 | * !!!国人编写的一些框架千万不要傻逼的看jar中反编译的java代码,IDEA会提示你下载有javadoc的源码,下过来, 103 | 中文注解注解起飞,舒服(例如这个通用Mapper) 104 | 105 | * 使用MessageFormat可以将字符串中的若干标识符替换为指定文本. 106 | 例如"My name is {}",可以将指定文本填充到{}; 107 | 或"My name {0} {1}",可以将一个String[]数组中的元素一次填充到{0},{1} 108 | 109 | 110 | ##### 配置Mybatis 111 | 1. 引入依赖:(此处需要添加version的原因是,该jar是mybatis提供的,spring无法自动提供版本号) 112 | > 113 | 114 | org.mybatis.spring.boot 115 | mybatis-spring-boot-starter 116 | ${mybatis-spring-boot-starter.version} 117 | 118 | 119 | mysql 120 | mysql-connector-java 121 | runtime 122 | 123 | > 124 | 125 | 126 | 2. 在Application类上增加注解@MapperScan("com.zx.springmybatis.dao"),扫描dao层 127 | 128 | 3. 然后就可以直接在dao类上使用mybatis注解了 129 | 130 | 4. 如下配置开启驼峰: 131 | > 132 | mybatis: 133 | configuration: 134 | #开启驼峰 135 | map-underscore-to-camel-case: true 136 | > 137 | 138 | 139 | 5. 如果需要使用mapper.xml,只需要在yml添加如下即可: 140 | > 141 | mapper-locations: classpath:mapper/*.xml #xml文件内容 142 | type-aliases-package: com.zx.springmybatis.entity #实体类包 143 | > 144 | 145 | #### 配置Druid - 未完全整合spring boot 146 | 4. 引入Druid依赖: 147 | > 148 | 149 | com.alibaba 150 | druid 151 | ${druid.version} 152 | 153 | > 154 | 155 | 5. 在yml文件中配置以spring.datasource开头的配置(具体配置参数可看DruidDataSource类源码) 156 | 157 | 6. 新建配置类,将DruidDataSource加入bean,并将yml中配置的参数注入 158 | 159 | @Configuration 160 | public class DruidDataSourceConfiguration { 161 | @Bean 162 | @ConfigurationProperties(prefix = "spring.datasource") 163 | public DataSource druidDataSource() { 164 | DruidDataSource druidDataSource = new DruidDataSource(); 165 | return druidDataSource; 166 | } 167 | } 168 | 169 | 7. 配置Servlet(原web.xml): 170 | 171 | @WebServlet(urlPatterns = "/druid/*", 172 | initParams = { 173 | @WebInitParam(name = "allow",value = ""),// IP白名单 (没有配置或者为空,则允许所有访问) 174 | @WebInitParam(name = "deny",value = ""),// IP黑名单 (存在共同时,deny优先于allow) 175 | @WebInitParam(name = "loginUsername",value = "zx"),//用户名 176 | @WebInitParam(name = "loginPassword",value = "970389"),//密码 177 | @WebInitParam(name = "resetEnable",value = "false")// 禁用HTML页面上的“Reset All”功能 178 | }) 179 | public class DruidStatViewServlet extends StatViewServlet{ 180 | } 181 | 182 | 8. 配置Filter: 183 | 184 | @WebFilter(filterName="druidWebStatFilter",urlPatterns="/*", 185 | initParams={ 186 | @WebInitParam(name="exclusions",value="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*")// 忽略资源 187 | }) 188 | public class DruidStatFilter extends WebStatFilter{ 189 | } 190 | 191 | 9. 在Application类上加上@ServletComponentScan注解,让Servlet配置生效 192 | 193 | 194 | #### Spring Boot 整合 Druid 195 | * Druid的功能佷强大,包括sql记录/session监控/请求uri记录等 196 | * 依赖 197 | > 198 | 199 | com.alibaba 200 | druid-spring-boot-starter 201 | 1.1.6 202 | 203 | > 204 | * 配置属性 205 | > 206 | spring: 207 | datasource: 208 | # 可自动识别 209 | driver-class-name: com.mysql.jdbc.Driver 210 | username: root 211 | password: 970389 212 | type: com.alibaba.druid.pool.DruidDataSource 213 | url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false 214 | # DataSource配置 215 | druid: 216 | # 初始容量 217 | initial-size: 10 218 | # 最大连接池个数 219 | max-active: 20 220 | # 最小空闲 221 | min-idle: 10 222 | # 获取连接最大等待时间 223 | max-wait: 3000 224 | # 是否缓存preparedStatement(PSCache),对游标提升巨大,建议oracle开启,mysql关闭 225 | pool-prepared-statements: false 226 | # 启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 227 | max-pool-prepared-statement-per-connection-size: 0 228 | # 检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 229 | validation-query: select 'x' 230 | # 检测连接是否有效的超时时间。秒,底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 231 | validation-query-timeout: 30 232 | # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 233 | test-on-borrow: false 234 | # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 235 | test-on-return: false 236 | # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 237 | test-while-idle: true 238 | # 驱逐策略间隔,如果连接空闲时间大于minEvictableIdleTimeMillis,则关闭 239 | time-between-eviction-runs-millis: 60000 240 | # 在池中的最小生存时间 241 | min-evictable-idle-time-millis: 30000 242 | # 在池中的最大生存时间 243 | max-evictable-idle-time-millis: 600000 244 | # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 245 | keep-alive: true 246 | # 连接初始化时,执行的sql 247 | connection-init-sqls: 248 | # 开启的过滤器,常用的有 监控统计:stat 日志:log4j 防御sql注入:wall 249 | filters: stat,wall,log4j 250 | # 合并多个dataSource的监控记录 251 | use-global-data-source-stat: true 252 | 253 | # 监控配置 254 | # 是否启用stat-filter默认值true 255 | web-stat-filter.enabled: true 256 | # 匹配的uri 257 | web-stat-filter.url-pattern: /* 258 | # 忽略的uri 259 | web-stat-filter.exclusions: *.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* 260 | # 是否启用session统计 261 | web-stat-filter.session-stat-enable: false 262 | # web-stat-filter.session-stat-max-count: 263 | # web-stat-filter.principal-session-name: 264 | # web-stat-filter.principal-cookie-name: 265 | # 监控单个url调用的sql列表。 266 | web-stat-filter.profile-enable: true 267 | # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置 268 | #是否启用监控界面默认值true 269 | stat-view-servlet.enabled: true 270 | # web.xml的url-pattern,也就是访问/druid/*访问到该servlet 271 | stat-view-servlet.url-pattern: /druid/* 272 | # 允许清空统计数据 273 | stat-view-servlet.reset-enable: true 274 | # 用户名 275 | stat-view-servlet.login-username: zx 276 | # 密码 277 | stat-view-servlet.login-password: 1223456 278 | # ip白名单 279 | stat-view-servlet.allow: 280 | # ip黑名单 281 | stat-view-servlet.deny: 282 | # 过滤器配置 283 | filter: 284 | stat: 285 | # 聚合sql 开启慢sql查询 286 | merge-sql: true 287 | # 是否开启慢sql查询 288 | log-slow-sql: true 289 | # 超过多少时间为慢sql 开启慢sql查询 290 | slow-sql-millis: 3000 291 | # 安全配置,防止sql注入. 具体参数可查看文档,包括禁止各类增删查改的操作 292 | # wall: 293 | # config: 294 | > 295 | 296 | 297 | #### 整合通用Mapper 298 | 1. 导入依赖: 299 | 300 | 301 | tk.mybatis 302 | mapper-spring-boot-starter 303 | ${mapper-spring-boot-starter.version} 304 | 305 | 306 | 307 | com.github.pagehelper 308 | pagehelper-spring-boot-starter 309 | ${pagehelper-spring-boot-starter.version} 310 | 311 | 2. 创建CommonMapper.java(注意。不能让@MapperScan("com.zx.springmybatis.dao")扫描到该类),其他所有mapper需要 !继承 !它。 312 | 313 | public interface CommonMapper extends Mapper,MySqlMapper { 314 | } 315 | 316 | 3. 其他mapper继承他即可。 317 | 318 | 4. 以上,除了通用mapper,pageHelper也已经可以使用(ps:startPage方法后必须紧跟查询语句;返回的PageInfo中会包含许多分页信息): 319 | 320 | public PageInfo getAllForPage(Integer pageNum, Integer pageSize) { 321 | pageNum = pageNum == null ? 1 : pageNum; 322 | pageSize = pageSize == null ? 10 : pageSize; 323 | 324 | PageHelper.startPage(pageNum,pageSize); 325 | List userList = userMapper.selectAll(); 326 | PageInfo pageInfo = new PageInfo<>(userList); 327 | 328 | log.info("pageInfo:{}",pageInfo); 329 | return pageInfo; 330 | } 331 | 5. 主键回写。在主键字段上增加@GeneratedValue(generator = "JDBC")这样的注解,还有uuid等,即可回写。 332 | 该回写是在传入的实体对象中,原本为空的主键被赋值,而不是直接返回。 333 | 334 | 6. 注意:insertSelective():保存一个实体,null的属性不会保存,会使用数据库默认值; 335 | insert():保存一个实体,null的属性也会保存,不会使用数据库默认值; 336 | update的方法也是一样。带Selective的才使用默认值 337 | 338 | 7. Example使用: 339 | > 340 | Example example = new Example(User.class)//传入实体类对象构造 341 | .selectProperties("id", "name")//设置要查询的字段 342 | .excludeProperties("id");//设置不查询的字段,与要查询同时设置,要查询的优先 343 | example.orderBy("id").desc();//排序 344 | example.createCriteria();//其他方法类似,基本都能用方法名理解 345 | .andLessThan("id","4");//查询属性小于该值的记录 346 | .andGreaterThan("id","4");//查询属性大于该值的记录 347 | .andAllEqualTo(temp);//查询字段值等于该对象的属性值的记录,所有属性。 348 | .andEqualTo(temp);//查询字段值等于该对象的属性值的记录,非空属性。 349 | .andBetween("name","a","c");//between查询 350 | .andCondition("name = 'a' or name ='b'");//可以直接使用sql查询,此处输入where后面的字符 351 | 352 | List userList = userMapper.selectByExample(example); 353 | > 354 | 355 | 8. 修改操作的使用: 356 | > 357 | public void updateGradeById(Long gradeId,Grade grade) { 358 | 359 | Example example = new Example(Grade.class); 360 | example.createCriteria().andEqualTo("id", gradeId); 361 | 362 | int i = gradeMapper.updateByExampleSelective(grade, example); 363 | //根据id直接更新 364 | //gradeMapper.updateByExampleSelective(); 365 | System.out.println("更新条数:" + i); 366 | } 367 | > 368 | 369 | #### 输出MyBatisSQL语句 370 | * 在yml中如下配置(com.zx.springmybatis.dao为自己的包名): 371 | > 372 | # 输出MyBatis语句,trace会输出结果,debug只输出语句 373 | logging: 374 | level: 375 | com: 376 | zx: 377 | springmybatis: 378 | dao: debug 379 | > 380 | * 或使用logback,如该博客配置:http://blog.csdn.net/qincidong/article/details/76122727 381 | 在logback.xml中配置: 382 | 383 | #### 整合MyBatisGenerator 384 | 1. 在pom.xml中添加属性如下(注释的xml,是因为不想生成xml文件,直接用注解形式的): 385 | > 386 | 387 | 388 | ${basedir}/src/main/java 389 | tk.mybatis.mapper.mapper 390 | tk.mybatis.mapper.model 391 | 392 | 393 | 394 | 395 | 3.4.4 396 | 5.1.44 397 | > 398 | 399 | 2. 增加maven插件,其参数由上面提供 400 | > 401 | 402 | org.mybatis.generator 403 | mybatis-generator-maven-plugin 404 | 1.3.5 405 | 406 | ${basedir}/src/main/resources/generator/generatorConfig.xml 407 | true 408 | true 409 | 410 | 411 | 412 | mysql 413 | mysql-connector-java 414 | ${mysql.version} 415 | 416 | 417 | tk.mybatis 418 | mapper 419 | ${mapper.version} 420 | 421 | 422 | 423 | > 424 | 425 | 3. 在resource下新增generator/generatorConfig.xml文件,其参数由下面的配置文件提供 426 | 427 | 4. 在同目录下新增config.properties文件 428 | 429 | 5. 在pom.xml这一级目录的命令行窗口执行mvn mybatis-generator:generate即可(IDEA Terminal打开可直接在该目录运行) 430 | 431 | #### MyBatis注解-动态sql的几种实现方式 432 | 1. 最原始-直接在方法注释上写动态sql代码: 433 | > 434 | @Insert("") 438 | void addAll(List grades); 439 | > 440 | 441 | 2. 使用Provider和SQL语句构建器(若不适用构建器,自己手写sql也行): 442 | 不使用SQL构建器: 443 | > 444 | /** 445 | * 使用Provider批量增加 446 | */ 447 | @InsertProvider(type = Provider.class,method = "batchInsert") 448 | void addAll1(List list); 449 | 450 | /** 451 | * 使用内部类作为Provider 452 | */ 453 | class Provider{ 454 | /** 455 | * 返回String作为sql语句 456 | * 不使用SQL构建器 457 | * 此处的sql是原生sql 458 | * 459 | * 参数: map中存储了MyBatisMapper方法中的参数; 460 | * 如果方法只有一个参数,也可以直接写相同类型的参数直接接收; 461 | * 如果方法使用了@Param注解,则使用map用@Param的value作为key接收 462 | * 如果多个参数,且未使用@Param注解,则使用map,用索引作为key接收 463 | * 具体可以下断点自行查看map 464 | */ 465 | public String batchInsert(Map map) { 466 | List list = (List) map.get("list"); 467 | StringBuilder result = new StringBuilder(); 468 | result.append("INSERT INTO grade(name) VALUES"); 469 | list.stream().forEach(item ->{ 470 | result.append("(").append("\"" + item.getName() + "\"").append(")"); 471 | result.append(","); 472 | }); 473 | result.deleteCharAt(result.length()-1); 474 | return result.toString(); 475 | } 476 | } 477 | > 478 | 使用SQL构建器: 479 | SQL构建器使用教程(Mybatis官网): http://www.mybatis.org/mybatis-3/zh/statement-builders.html 480 | 此处不作例子了,我只能说,这个构建器构建不是批量增加等操作的sql极其方便,但如果是批量增加等sql,还不如自己拼接呢; 481 | 482 | 3. 增强型注解. 483 | * LanguageDriver接口: 484 | * createParameterHandler()方法: 485 | * 创建参数处理器,将预编译的sql的参数替换为真正内容,例如{name}/{1}这样的 486 | * createSqlSource(XNode)方法: 487 | * 创建SqlSource,它保存了从mapper.xml中读取出来的还未真正替换值的sql语句 488 | * createSqlSource(String)方法:它保存了从注解中读取出来的sql. 489 | * 该接口的实现有XMLLanguageDriver,然后xml类还有个子类是RawLanguageDriver; 490 | * XMLLanguageDriver是未解析的也就是写在xml或注解中的那样的sql. 491 | * RawLanguageDriver是解析后的,可以直接执行的原生sql.(源码注解:除非确保是原生sql,否则没有任何理由使用该类) 492 | * 自定义该接口: 493 | * 如上介绍,我们可以通过继承XMLLanguageDriver类,重写createSqlSource(String)方法来实现自己的需求; 494 | * 如下,就是我自己实现的一个通用的,可以对每个实体进行条件查询的扩展接口: 495 | > 496 | /** 497 | * author:ZhengXing 498 | * datetime:2017/11/28 0028 14:19 499 | * 通用条件查询语言驱动 500 | */ 501 | public class CommonConditionLanguageDriver extends XMLLanguageDriver{ 502 | /** 503 | * 重写父类方法,以便在条件查询时,将不为空属性,加入where条件, 504 | * 例如: 505 | * select * from user 506 | * 507 | * and username=#{username} 508 | * and password=#{password} 509 | * 510 | * parameterType:mapper中方法接收的参数,如果参数有多个,其值为map,当参数为多个时,无法获悉每个参数的类型(应该是) 511 | */ 512 | @Override 513 | public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { 514 | //追加where 515 | StringBuilder sql = new StringBuilder().append(""); 516 | //默认将该参数类型作为实体类类型处理,获取所有属性 517 | Field[] fields = parameterType.getDeclaredFields(); 518 | 519 | //遍历实体类的每个属性 520 | for (Field field : fields) { 521 | //将java中 userId形式的属性转换为数据库中 user_id形式的 522 | String sqlField = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()); 523 | //循环增加的语句 524 | String temp = "and sqlField=#{javaField} "; 525 | //将字符串中的自定义标识字符:javaField和sqlField替换 526 | temp = temp.replaceAll("javaField",field.getName()) 527 | .replaceAll("sqlField", sqlField); 528 | sql.append(temp); 529 | } 530 | sql.append(""); 531 | 532 | //增加"; 534 | //继续执行父类的方法实现,构建SqlSource 535 | return super.createSqlSource(configuration, script, parameterType); 536 | } 537 | } 538 | > 539 | 在Mapper中如下写法: 540 | > 541 | /** 542 | * 使用LanguageDriver进行通用的条件查询 543 | */ 544 | @Lang(CommonConditionLanguageDriver.class) 545 | @Select("select * from user") 546 | List findByCondition(User user); 547 | > 548 | 如此调用,即可查询出所有 name=a,password=aa的记录,而其他空的字段则被忽略 549 | > 550 | User user = new User().setName("a").setPassword("aa"); 551 | List a = userMapper.findByCondition(user); 552 | a.forEach(item-> System.out.println(a)); 553 | > 554 | 当然,这类通用的sql,在通用Mapper中都已经提供了. 555 | 556 | #### 单表查询和多表关联查询的选择 557 | * 一般来说,性能是多表占优.但是如果数据量大的话或许不一定. 558 | * 多表查询如果关联表过多性能很低. 559 | * 多表查询不方便使用缓存. 560 | * 多表查询如果遇到分库分表等情况,需要重写sql 561 | * 综上所述,推荐单表查询 562 | 563 | #### SpringCache + redis 实现注解缓存 564 | 1. 引入spring redis和spring cache依赖: 565 | > 566 | 567 | org.springframework.boot 568 | spring-boot-starter-cache 569 | 570 | 571 | 572 | org.springframework.boot 573 | spring-boot-starter-data-redis 574 | 575 | > 576 | 577 | 2. 在yml如下配置即可: 578 | > 579 | #缓存 580 | cache: 581 | #缓存名字 582 | cache-names: #该属性的接收类型为list,得在这样写才可以分为一个个元素 583 | - a 584 | - b 585 | - c 586 | #缓存过期时间 587 | cacheExpires: #自定义属性,也是list,用来配置缓存过期时间 588 | - 3600 589 | - 1 590 | - 0 591 | #缓存类型,同时引入guava包和redis时,不配置可能有bug 592 | type: redis 593 | #redis配置 594 | redis: 595 | host: 106.14.7.29 596 | port: 6379 597 | password: 970389 598 | pool: 599 | max-active: 10 600 | max-idle: 1 601 | min-idle: 0 602 | max-wait: 50000 603 | > 604 | 605 | 3. 在Application类上增加:@EnableCaching注解(也就表示可用该注解一键关闭所有缓存) 606 | 607 | 4. 对所有需要缓存的对象需要实现Serializable接口 608 | 609 | 5. 此时,两次执行如下语句,第二次已经无需进行数据库查询,并且未进入方法体(其实现为AOP): 610 | !!之前我一直以为其实现是AOP...后来我在@EnableCahcing注解中找到了..Mode参数, 611 | 才发现其默认实现是代理类,当然可以选择用aop(暂未深入,但aop的实现不也是用的代理类? 猜测可能代理类模式是自己重新实现,aop模式时直接复用springFrame的aop) 612 | > 613 | /** 614 | * 查询所有班级 615 | * 注意,@Cacheable中的cacheNames值需要在yml中配置,也就是spring.cache.cache-names 616 | */ 617 | @Cacheable(value = "redis") 618 | public List finAll() { 619 | log.info("查询所有班级"); 620 | return gradeMapper.selectAll(); 621 | } 622 | > 623 | 624 | 6. 此时如果查看redis中的key的话,会发现该程序自动缓存的所有key,都有个redis:\xac\xed\x00\x05t\x00这样的前缀, 625 | 其原因是使用了JDK默认的对象序列化方法Serializer.convert().而RedisTemplate类的两个泛型为空,导致一些问题; 626 | 只需要替换redis cache的默认序列化配置即可(其方法同样是在配置类中配置一个返回RedisTemplate类型的bean方法)(下面有介绍) 627 | 628 | 7. 自定义redis配置类,详见代码及其注释: 629 | > 630 | /** 631 | * author:ZhengXing 632 | * datetime:2017/11/29 0029 13:32 633 | * redis缓存配置类 634 | * 635 | * CachingConfigurerSupport该类使用空方法实现了CachingConfigurer接口, 636 | * 子类只需要实现想要自定义的方法即可配置 缓存管理器/主键生成器/缓存解析器/异常处理器等; 637 | * 如果不实现该接口,配置该类后,还需在注解中指定对应的keyGenerator才能生效 638 | * 639 | */ 640 | @Configuration 641 | public class RedisCacheConfig extends CachingConfigurerSupport{ 642 | 643 | //Spring构造的redis连接工厂 644 | @Autowired 645 | private RedisConnectionFactory redisConnectionFactory; 646 | 647 | //自定义的用来读取yml文件中每个缓存名对应的缓存过期时间的属性类 648 | @Autowired 649 | private CustomRedisCacheExpireProperties customRedisCacheExpireProperties; 650 | 651 | /** 652 | * 匿名内部类构建主键生成器 653 | * 其参数分别为 调用缓存的类(service)/调用缓存的方法/方法的参数列表 654 | */ 655 | @Bean 656 | @Override 657 | public KeyGenerator keyGenerator() { 658 | return (object,method,params)->{ 659 | //类名:方法名:参数[0]参数[1]... 660 | StringBuilder key = new StringBuilder(object.getClass().getSimpleName() + "-" + method.getName() + ":"); 661 | for (Object param : params) { 662 | //直接追加,只要该参数是基本类型或实现了toString方法,就没问题,否则会显示xx@hashcode那种类型的字符 663 | //如果参数过多,需要自定义key 664 | key.append(param.toString()); 665 | } 666 | return key.toString(); 667 | }; 668 | } 669 | 670 | /** 671 | * 配置RedisTemplate 672 | * 是为了替换默认的JDK的序列化器,使用默认的序列化器,key会乱码; 673 | * 674 | * 此处在Spring中的实现是,他有一个默认的RedisTemplate Bean,但使用了 675 | * @ConditionalOnMissingBean(type = RedisTemplate.class)这样一个注解, 676 | * 表示在我们没有配置自定义的bean的情况下,才使用它默认的bean 677 | */ 678 | @Bean 679 | public RedisTemplate redisTemplate() { 680 | //创建StringRedis模版 681 | StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory); 682 | // 使用Jackson2JsonRedisSerialize 替换默认序列化 683 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 684 | ObjectMapper objectMapper = new ObjectMapper(); 685 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 686 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 687 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 688 | 689 | //value使用jackJson序列化,key使用string序列化,string序列化不支持list等类型 690 | //stringRedisTemplate.setKeySerializer(new StringRedisSerializer());//不需要该设置,key也不会乱码. 691 | stringRedisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 692 | 693 | //InitializingBean接口提供的一个方法,在spring容器属性被初始化完成后再调用该方法 694 | stringRedisTemplate.afterPropertiesSet(); 695 | 696 | return stringRedisTemplate; 697 | } 698 | 699 | /** 700 | * 创建缓存管理器 701 | * 主要为了自定义若干cacheNames和缓存过期时间; 702 | * 703 | * 自定义该类后,如果缓存注解中使用了一个未配置的缓存名,并且,该类的一个dynamic属性为true, 704 | * 就会生成一个新的以该名字为名的{@link Cache}对象,放入集合; 705 | * 但如果给该缓存管理器配置了cacheNames(也就是调用了setCacheNames()方法),该dynamic属性就会被 706 | * 设置为false,将无法动态加入缓存名;那么就会抛出无法找到该缓存的异常; 707 | * 我觉得还是设置上比较好. 708 | */ 709 | @Bean 710 | @Override 711 | public CacheManager cacheManager() { 712 | RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate()); 713 | //默认的过期时间,会被每个缓存名自己的过期时间覆盖 714 | redisCacheManager.setDefaultExpiration(3600); 715 | /** 716 | * 启动时加载远程缓存; 不开启:每次第一次查询即使缓存中已经有旧的缓存,也不会读取到; 717 | * 开启后如果缓存中已有缓存,第一次查询就会从缓存中读取 718 | */ 719 | redisCacheManager.setLoadRemoteCachesOnStartup(true); 720 | //开启后,key会携带上cacheName作为前缀 721 | redisCacheManager.setUsePrefix(true); 722 | /** 723 | * 设置cacheNames,也可以在构造函数中设置,此处我使用在yml配置的cacheNames即可 724 | * 需要注意的是,显而易见,此处的RedisCacheManager还未注入yml中的cacheNames; 725 | * 所以如果使用redisCacheManager.getCacheNames()取出的将是空的; 726 | * 但是,如果使用setExpires()方法,设置好对应的cacheName和过期时间,还是能够生效的 727 | */ 728 | //redisCacheManager.setCacheNames(Arrays.asList(cacheNames)); 729 | //Collection cacheNames = redisCacheManager.getCacheNames(); 730 | 731 | //使用自定义的属性类,根据yml配置,生成缓存名和过期时间对应的map 732 | Map expires = customRedisCacheExpireProperties.generateExpireMap(); 733 | //设置每个缓存对应的过期时间 734 | redisCacheManager.setExpires(expires); 735 | //给缓存管理器设置上缓存名s 736 | redisCacheManager.setCacheNames(customRedisCacheExpireProperties.getCacheNames()); 737 | 738 | 739 | return redisCacheManager; 740 | } 741 | 742 | /** 743 | * 自定义缓存异常处理器. 744 | * 该CacheErrorHandler接口只有一个实现类SimpleCacheErrorHandler.只是抛出了所有异常未做任何处理 745 | * 有若干个方法,分别处理获取/修改/放入/删除缓存异常. 746 | * 若有需要.可自定义实现,比如因为缓存不是必须的,那就可以只做日志记录,不再抛出异常 747 | * 748 | */ 749 | @Bean 750 | @Override 751 | public CacheErrorHandler errorHandler() { 752 | return new SimpleCacheErrorHandler(){ 753 | @Override 754 | public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { 755 | super.handleCacheGetError(exception, cache, key); 756 | } 757 | }; 758 | } 759 | 760 | /** 761 | * 自定义缓存解析器(该类必须是线程安全的) 762 | * 763 | * 其默认实现是SimpleCacheResolver 764 | * 765 | */ 766 | @Override 767 | public CacheResolver cacheResolver() { 768 | return super.cacheResolver(); 769 | } 770 | } 771 | > 772 | 773 | 8. 自定义yml spring.cache属性类,详见代码及其注释: 774 | > 775 | /** 776 | * author:ZhengXing 777 | * datetime:2017/12/1 0001 12:46 778 | * 自定义的redis缓存中的过期时间属性 779 | */ 780 | @Data 781 | @ConfigurationProperties(prefix = "spring.cache") 782 | @Component 783 | public class CustomRedisCacheExpireProperties { 784 | //该属性在spring cache框架自己的类中也会被获取 785 | //此处获取是为了对长度进行校验,防止 缓存名字 - 缓存时间 没有一一匹配 786 | private List cacheNames; 787 | 788 | //缓存时间,和缓存名一一对应 789 | private List cacheExpires; 790 | 791 | /** 792 | * 生成Map,用来放入RedisManager中 793 | */ 794 | public Map generateExpireMap() { 795 | Map expireMap = new HashMap<>(); 796 | /** 797 | * 校验参数值 798 | */ 799 | //如果未配置cacheNames属性,返回空map 800 | //如果未配置cacheExpires属性,也返回空map 801 | if (CollectionUtils.isEmpty(cacheNames) || CollectionUtils.isEmpty(cacheExpires)) 802 | return expireMap; 803 | //长度校验:只要数组不为空,有x个cacheNames,就需要x个cacheExpires,如果某个name无需缓存时间,设置为0即可 804 | //其内部实现就是使用该Map生成若干个RedisCacheMetadata,该对象和cacheName一一对应,并且其中的默认过期时间就是0 805 | //不对.我在redis中试了下,将key过期时间设为0或负数,该key会直接过期. 806 | //找了很久..没找到其判断过期时间的代码 807 | if(cacheNames.size() != cacheExpires.size()) 808 | //此处随便抛出一个非法状态异常,可自定义异常抛出 809 | throw new IllegalStateException("cacheExpires设置非法.cacheNames和cacheExpires长度不一致"); 810 | //遍历cacheNames 811 | for (int i = 0; i < cacheNames.size(); i++) { 812 | //只有当cacheExpires设置的大于0时,才放入map 813 | long expire = cacheExpires.get(i); 814 | if (expire > 0) 815 | expireMap.put(cacheNames.get(i),expire); 816 | } 817 | return expireMap; 818 | } 819 | } 820 | > 821 | 822 | 823 | #### SpringCache注解 824 | * 注意: 825 | * spEl表达式如果不想使用,需要用两个单引号转移 826 | 827 | * @CacheConfig:注解在类上,表示该类方法上的注解都默认使用该注解定义的配置; 828 | 配置该注解后,方法上的注解也可以配置自己的属性,覆盖该注解; 829 | 可配置cacheNames/keyGenerator/cacheManager/cacheResolver 830 | 831 | * @Cacheable:(查询)注解在方法上,表示执行该方法前先从缓存中读取数据,没有再从方法中读取; 832 | * cacheNames: 缓存名,也就是配置在yml中的属性(如果不配置@CacheConfig,它是必须的) 833 | 需要注意的时,如果配置了自定义的RedisManager,即使RedisManager和yml中都没有配置的name也是可以使用的; 834 | 研表究明...当配置了自定义的缓存管理器后,yml中的cacheNames不会在再被使用 835 | * key: 缓存的Key,可配置,不配置使用spring默认的SimpleKeyGenerator生成; 支持spEl表达式 836 | 除了上面使用方法参数作为Key以外,Spring还为我们提供了一个root对象可以生成key。通过root对象我们还可以获取到 837 | -------1.methodName 当前方法名 #root.methodName 838 | -------2.method 当前方法 #root.method.name 839 | -------3.target 当前被动用对象 #root.target 840 | -------4.targetClass 当前被调用对象Class#root.targetClass 841 | -------5.args 当前方法参数组成的数组 #root.args[0] 842 | -------6.caches 当前被调用方法所使用的Cache #root.caches[0],name 843 | 使用root作为key时,可以不用写root直接@Cache(key="caches[1].name"),他默认是使用#root的 844 | * condition: 缓存对象的条件,非必须,SpEL表达式,只有满足条件的内容才会被缓存, 845 | 例如#param.length() < 3,表示参数param长度小于3时才被缓存; 846 | * unless: 另一个缓存条件参数,SpEL表达式,它不同于condition参数的地方在于它的判断时机, 847 | 该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断 848 | * keyGenerator: 指定key生成器;该参数和key参数互斥,配置了某一个就不能配置另一个; 849 | * cacheManager: 指定缓存管理器; 850 | * cacheResolver: 指定缓存解析器; 851 | * sync: 缓存为空时,如果多个线程同时调用底层方法(数据库),则线程阻塞的调用,尝试为相同的key加载同样的value. 852 | 它会导致几个问题:1.不支持unless参数; 2.只能指定一个缓存; 3.不能与其他缓存相关的操作组合; 默认为false. 853 | 它适用于那种高并发下的,某个缓存正好过期的场景. 854 | 855 | * @CachePut:(更新)无论缓存是否存在,都会将执行结果放入缓存; 856 | 用于insert方法,或update(如果时更新,需要将更新后的结果返回) 857 | 858 | * @CacheEvict:(删除)删除指定缓存;用于删除或更新操作 859 | * 雷同参数不再赘述.自行查看 860 | * allEntries: 是否删除所有条目(整个cacheNames),默认只删除当前key. 861 | 注意,当它为true时,不允许指定该注解的key参数 862 | * beforeInvocation: 是否在方法调用前删除; 863 | 设置为true,无论结果如何该缓存都会被删除,(例如当方法异常); 864 | 默认为false,也就是当该方法执行成功之后才会删除缓存(如果抛出异常,则不会删除) 865 | 866 | * @Caching:使用该注解在同一个方法上叠加多个缓存注解; 867 | 该注解的成员变量如下(我就不想再说什么了,一目了然): 868 | > 869 | Cacheable[] cacheable() default {}; 870 | CachePut[] put() default {}; 871 | CacheEvict[] evict() default {}; 872 | > 873 | 874 | * 自定义注解:只需要在注解类上增加上面这些注解,再将注解类注解到方法上,一样可以 875 | 876 | 877 | #### SpringCache使用设想 878 | 对于缓存的使用,之前我觉得有一些问题. 879 | 例如,有一个根据id查询user的方法使用缓存; 880 | 那么,如果有一个修改user的方法,使用@CachePut注解,将修改后的值直接放入缓存. 881 | 或者其他类似的场景,需要在方法中,修改其他方法需要读取的缓存. 882 | 就需要将@CachePut/@CacheEvict上的注解上的key和@Cacheable上的key对应起来; 883 | 884 | 例如我目前的写法,根据简单类名/方法名/参数值生成缓存. 885 | 我的查询方法是 CacheService类的findOneByGradeId方法. 886 | 就需要在新增缓存值的方法上这样写:@CachePut(key = "'CacheService-findOneByGradeId-' + #result.id") 887 | 那如果我需要修改类名/方法名等,岂不是爆炸了. 888 | 889 | 然后我突然顿悟.他是有个cacheNames的,可配置多个不同的缓存前缀; 890 | 那么,我就可以将每个类或有关联的几个缓存方法,设置上各自的cacheName. 891 | 然后将缓存的key都改为简单的可动态编写的.例如几个参数的hashcode等. 892 | (或者直接每个缓存关联使用一个cacheName也可,只是这样名字的数量可能会很多) 893 | 然后,在缓存配置类的缓存管理器中不再设置缓存名集合,这样就可以动态生成缓存名了. 894 | 然后如果不需要默认过期时间的缓存,照旧可以在yml中自定义过期时间. 895 | 896 | 再或者,可以自定义一个注解,注解在类上,包含了类中的cacheName和其过期时间, 897 | 然后就可以在启动时扫描所有类,解析出数据,放入缓存管理器中. 898 | 899 | 900 | 901 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.zx 7 | springmybatis 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | SpringBoot-MyBatis-RedisCache 12 | spring整合mybatis和通用mapper 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 1.5.8.RELEASE 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 1.1.6 27 | 1.3.1 28 | 2.1.5 29 | 1.1.5 30 | 1.2.3 31 | 1.3.5 32 | 33 | 34 | 35 | 36 | ${basedir}/src/main/java 37 | tk.mybatis.mapper.mapper 38 | tk.mybatis.mapper.model 39 | 40 | 41 | 42 | 43 | 3.4.4 44 | 5.1.44 45 | 23.5-jre 46 | 47 | 48 | 49 | 50 | org.mybatis.spring.boot 51 | mybatis-spring-boot-starter 52 | 1.3.1 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-web 57 | 58 | 59 | 60 | mysql 61 | mysql-connector-java 62 | runtime 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | 71 | org.projectlombok 72 | lombok 73 | provided 74 | 75 | 76 | 77 | org.mybatis.spring.boot 78 | mybatis-spring-boot-starter 79 | ${mybatis-spring-boot-starter.version} 80 | 81 | 82 | 83 | 84 | 85 | com.alibaba 86 | druid-spring-boot-starter 87 | ${druid.version} 88 | 89 | 90 | 91 | tk.mybatis 92 | mapper-spring-boot-starter 93 | ${mapper-spring-boot-starter.version} 94 | 95 | 96 | 97 | com.github.pagehelper 98 | pagehelper-spring-boot-starter 99 | ${pagehelper-spring-boot-starter.version} 100 | 101 | 102 | 103 | com.google.guava 104 | guava 105 | ${guava.version} 106 | 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-starter-cache 111 | 112 | 113 | 114 | org.springframework.boot 115 | spring-boot-starter-data-redis 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.springframework.boot 125 | spring-boot-maven-plugin 126 | 127 | 128 | 129 | org.mybatis.generator 130 | mybatis-generator-maven-plugin 131 | 1.3.5 132 | 133 | ${basedir}/src/main/resources/generator/generatorConfig.xml 134 | true 135 | true 136 | 137 | 138 | 139 | mysql 140 | mysql-connector-java 141 | ${mysql.version} 142 | 143 | 144 | tk.mybatis 145 | mapper 146 | ${mapper.version} 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/SpringMyBatisApplication.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.mybatis.spring.annotation.MapperScan; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.ServletComponentScan; 8 | import org.springframework.cache.annotation.EnableCaching; 9 | 10 | @SpringBootApplication 11 | @ServletComponentScan 12 | @MapperScan("com.zx.springmybatis.dao") 13 | @EnableCaching 14 | @Slf4j 15 | public class SpringMyBatisApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SpringMyBatisApplication.class, args); 19 | log.info("---------------------------------"); 20 | log.warn("----------------------------------"); 21 | log.debug("--------------------------------"); 22 | log.error("------------------------------------"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/cache/CustomRedisCacheExpireProperties.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.cache; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.CollectionUtils; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * author:ZhengXing 14 | * datetime:2017/12/1 0001 12:46 15 | * 自定义的redis缓存中的过期时间属性 16 | */ 17 | @Data 18 | @ConfigurationProperties(prefix = "spring.cache") 19 | @Component 20 | public class CustomRedisCacheExpireProperties { 21 | //该属性在spring cache框架自己的类中也会被获取 22 | //此处获取是为了对长度进行校验,防止 缓存名字 - 缓存时间 没有一一匹配 23 | private List cacheNames; 24 | 25 | //缓存时间,和缓存名一一对应 26 | private List cacheExpires; 27 | 28 | /** 29 | * 生成Map,用来放入RedisManager中 30 | */ 31 | public Map generateExpireMap() { 32 | Map expireMap = new HashMap<>(); 33 | /** 34 | * 校验参数值 35 | */ 36 | //如果未配置cacheNames属性,返回空map 37 | //如果未配置cacheExpires属性,也返回空map 38 | if (CollectionUtils.isEmpty(cacheNames) || CollectionUtils.isEmpty(cacheExpires)) 39 | return expireMap; 40 | //长度校验:只要数组不为空,有x个cacheNames,就需要x个cacheExpires,如果某个name无需缓存时间,设置为0即可 41 | //其内部实现就是使用该Map生成若干个RedisCacheMetadata,该对象和cacheName一一对应,并且其中的默认过期时间就是0 42 | //不对.我在redis中试了下,将key过期时间设为0或负数,该key会直接过期. 43 | //找了很久..没找到其判断过期时间的代码 44 | if(cacheNames.size() != cacheExpires.size()) 45 | //此处随便抛出一个非法状态异常,可自定义异常抛出 46 | throw new IllegalStateException("cacheExpires设置非法.cacheNames和cacheExpires长度不一致"); 47 | //遍历cacheNames 48 | for (int i = 0; i < cacheNames.size(); i++) { 49 | //只有当cacheExpires设置的大于0时,才放入map,否则就使用默认的过期时间 50 | long expire = cacheExpires.get(i); 51 | if (expire > 0) 52 | expireMap.put(cacheNames.get(i),expire); 53 | } 54 | return expireMap; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/cache/RedisCacheConfig.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.cache; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.cache.Cache; 8 | import org.springframework.cache.CacheManager; 9 | import org.springframework.cache.annotation.CachingConfigurerSupport; 10 | import org.springframework.cache.interceptor.CacheErrorHandler; 11 | import org.springframework.cache.interceptor.CacheResolver; 12 | import org.springframework.cache.interceptor.KeyGenerator; 13 | import org.springframework.cache.interceptor.SimpleCacheErrorHandler; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.data.redis.cache.RedisCacheManager; 17 | import org.springframework.data.redis.connection.RedisConnectionFactory; 18 | import org.springframework.data.redis.core.RedisTemplate; 19 | import org.springframework.data.redis.core.StringRedisTemplate; 20 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 21 | 22 | import java.util.*; 23 | 24 | 25 | /** 26 | * author:ZhengXing 27 | * datetime:2017/11/29 0029 13:32 28 | * redis缓存配置类 29 | * 30 | * CachingConfigurerSupport该类使用空方法实现了CachingConfigurer接口, 31 | * 子类只需要实现想要自定义的方法即可配置 缓存管理器/主键生成器/缓存解析器/异常处理器等; 32 | * 如果不实现该接口,配置该类后,还需在注解中指定对应的keyGenerator才能生效 33 | * 34 | */ 35 | @Configuration 36 | public class RedisCacheConfig extends CachingConfigurerSupport{ 37 | 38 | //Spring构造的redis连接工厂 39 | @Autowired 40 | private RedisConnectionFactory redisConnectionFactory; 41 | 42 | //自定义的用来读取yml文件中每个缓存名对应的缓存过期时间的属性类 43 | @Autowired 44 | private CustomRedisCacheExpireProperties customRedisCacheExpireProperties; 45 | 46 | /** 47 | * 匿名内部类构建主键生成器 48 | * 其参数分别为 调用缓存的类(service)/调用缓存的方法/方法的参数列表 49 | */ 50 | @Bean 51 | @Override 52 | public KeyGenerator keyGenerator() { 53 | return (object,method,params)->{ 54 | //类名:方法名:参数[0]参数[1]... 55 | StringBuilder key = new StringBuilder(object.getClass().getSimpleName() + "-" + method.getName() + "-"); 56 | for (Object param : params) { 57 | //直接追加,只要该参数是基本类型或实现了toString方法,就没问题,否则会显示xx@hashcode那种类型的字符 58 | //如果参数过多,需要自定义key 59 | key.append(param.toString()); 60 | } 61 | return key.toString(); 62 | }; 63 | } 64 | 65 | /** 66 | * 配置RedisTemplate 67 | * 是为了替换默认的JDK的序列化器,使用默认的序列化器,key会乱码; 68 | * 69 | * 此处在Spring中的实现是,他有一个默认的RedisTemplate Bean,但使用了 70 | * @ConditionalOnMissingBean(type = RedisTemplate.class)这样一个注解, 71 | * 表示在我们没有配置自定义的bean的情况下,才使用它默认的bean 72 | */ 73 | @Bean 74 | public RedisTemplate redisTemplate() { 75 | //创建StringRedis模版 76 | StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory); 77 | // 使用Jackson2JsonRedisSerialize 替换默认序列化 78 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 79 | ObjectMapper objectMapper = new ObjectMapper(); 80 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 81 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 82 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 83 | 84 | //value使用jackJson序列化,key使用string序列化,string序列化不支持list等类型 85 | // stringRedisTemplate.setKeySerializer(new StringRedisSerializer());//不需要该设置,key也不会乱码. 86 | stringRedisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 87 | 88 | //InitializingBean接口提供的一个方法,在spring容器属性被初始化完成后再调用该方法 89 | stringRedisTemplate.afterPropertiesSet(); 90 | 91 | return stringRedisTemplate; 92 | } 93 | 94 | /** 95 | * 创建缓存管理器 96 | * 主要为了自定义若干cacheNames和缓存过期时间; 97 | * 98 | * 自定义该类后,如果缓存注解中使用了一个未配置的缓存名,并且,该类的一个dynamic属性为true, 99 | * 就会生成一个新的以该名字为名的{@link Cache}对象,放入集合; 100 | * 但如果给该缓存管理器配置了cacheNames(也就是调用了setCacheNames()方法),该dynamic属性就会被 101 | * 设置为false,将无法动态加入缓存名;那么就会抛出无法找到该缓存的异常; 102 | * 我觉得还是设置上比较好. 103 | */ 104 | @Bean 105 | @Override 106 | public CacheManager cacheManager() { 107 | RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate()); 108 | //默认的过期时间,会被每个缓存名自己的过期时间覆盖 109 | redisCacheManager.setDefaultExpiration(3600); 110 | /** 111 | * 启动时加载远程缓存; 不开启:每次第一次查询即使缓存中已经有旧的缓存,也不会读取到; 112 | * 开启后如果缓存中已有缓存,第一次查询就会从缓存中读取 113 | */ 114 | redisCacheManager.setLoadRemoteCachesOnStartup(true); 115 | //开启后,key会携带上cacheName作为前缀 116 | redisCacheManager.setUsePrefix(true); 117 | /** 118 | * 设置cacheNames,也可以在构造函数中设置,此处我使用在yml配置的cacheNames即可 119 | * 需要注意的是,显而易见,此处的RedisCacheManager还未注入yml中的cacheNames; 120 | * 所以如果使用redisCacheManager.getCacheNames()取出的将是空的; 121 | * 但是,如果使用setExpires()方法,设置好对应的cacheName和过期时间,还是能够生效的 122 | */ 123 | // redisCacheManager.setCacheNames(Arrays.asList(cacheNames)); 124 | // Collection cacheNames = redisCacheManager.getCacheNames(); 125 | 126 | //使用自定义的属性类,根据yml配置,生成缓存名和过期时间对应的map 127 | Map expires = customRedisCacheExpireProperties.generateExpireMap(); 128 | //设置每个缓存对应的过期时间 129 | redisCacheManager.setExpires(expires); 130 | //给缓存管理器设置上缓存名s 131 | redisCacheManager.setCacheNames(customRedisCacheExpireProperties.getCacheNames()); 132 | 133 | 134 | return redisCacheManager; 135 | } 136 | 137 | /** 138 | * 自定义缓存异常处理器. 139 | * 该CacheErrorHandler接口只有一个实现类SimpleCacheErrorHandler.只是抛出了所有异常未做任何处理 140 | * 有若干个方法,分别处理获取/修改/放入/删除缓存异常. 141 | * 若有需要.可自定义实现,比如因为缓存不是必须的,那就可以只做日志记录,不再抛出异常 142 | * 143 | */ 144 | @Bean 145 | @Override 146 | public CacheErrorHandler errorHandler() { 147 | return new SimpleCacheErrorHandler(){ 148 | @Override 149 | public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { 150 | super.handleCacheGetError(exception, cache, key); 151 | } 152 | }; 153 | } 154 | 155 | /** 156 | * 自定义缓存解析器(该类必须是线程安全的) 157 | * 解析该缓存由哪些(可以多个) cacheNames处理 158 | * 159 | * 其默认实现是SimpleCacheResolver 160 | * 161 | */ 162 | @Override 163 | public CacheResolver cacheResolver() { 164 | return super.cacheResolver(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/druid/DruidDataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.druid; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import javax.sql.DataSource; 9 | 10 | /** 11 | * author:Administrator 12 | * datetime:2017/11/7 0007 09:37 13 | * druid 数据源 配置类 14 | */ 15 | //@Configuration 16 | public class DruidDataSourceConfiguration { 17 | @Bean 18 | @ConfigurationProperties(prefix = "spring.datasource") 19 | public DataSource druidDataSource() { 20 | DruidDataSource druidDataSource = new DruidDataSource(); 21 | return druidDataSource; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/druid/DruidStatFilter.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.druid; 2 | 3 | import com.alibaba.druid.support.http.WebStatFilter; 4 | 5 | import javax.servlet.annotation.WebFilter; 6 | import javax.servlet.annotation.WebInitParam; 7 | 8 | /** 9 | * author:Administrator 10 | * datetime:2017/11/7 0007 10:10 11 | * druid 过滤器 12 | */ 13 | //@WebFilter(filterName="druidWebStatFilter",urlPatterns="/*", 14 | // initParams={ 15 | // @WebInitParam(name="exclusions",value="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*")// 忽略资源 16 | // }) 17 | public class DruidStatFilter extends WebStatFilter{ 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/druid/DruidStatViewServlet.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.druid; 2 | 3 | import com.alibaba.druid.support.http.StatViewServlet; 4 | 5 | import javax.servlet.annotation.WebInitParam; 6 | import javax.servlet.annotation.WebServlet; 7 | 8 | /** 9 | * author:Administrator 10 | * datetime:2017/11/7 0007 10:04 11 | * druid servlet配置 12 | */ 13 | //@WebServlet(urlPatterns = "/druid/*", 14 | //initParams = { 15 | // @WebInitParam(name = "allow",value = ""),// IP白名单 (没有配置或者为空,则允许所有访问) 16 | // @WebInitParam(name = "deny",value = ""),// IP黑名单 (存在共同时,deny优先于allow) 17 | // @WebInitParam(name = "loginUsername",value = "zx"),//用户名 18 | // @WebInitParam(name = "loginPassword",value = "970389"),//密码 19 | // @WebInitParam(name = "resetEnable",value = "false")// 禁用HTML页面上的“Reset All”功能 20 | //}) 21 | public class DruidStatViewServlet extends StatViewServlet{ 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/config/mybatis/CommonMapper.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.config.mybatis; 2 | 3 | import tk.mybatis.mapper.common.Mapper; 4 | import tk.mybatis.mapper.common.MySqlMapper; 5 | 6 | /** 7 | * author:ZhengXing 8 | * datetime:2017/11/16 0016 15:16 9 | * 通用Mapper顶层接口,所有mapper需要继承他 10 | */ 11 | public interface CommonMapper extends Mapper,MySqlMapper { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.controller; 2 | 3 | import com.github.pagehelper.PageInfo; 4 | import com.zx.springmybatis.entity.User; 5 | import com.zx.springmybatis.service.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * author:Administrator 15 | * datetime:2017/11/7 0007 10:16 16 | */ 17 | @RestController 18 | @RequestMapping("/") 19 | public class TestController { 20 | 21 | @Autowired 22 | private UserService userService; 23 | 24 | @GetMapping("/getAll") 25 | public List getAll() { 26 | return userService.getAll(); 27 | } 28 | 29 | @GetMapping("/page") 30 | public PageInfo page(Integer pageNum,Integer pageSize){ 31 | return userService.getAllForPage(pageNum, pageSize); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/dao/GradeMapper.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.dao; 2 | 3 | 4 | import com.zx.springmybatis.config.mybatis.CommonMapper; 5 | import com.zx.springmybatis.entity.Grade; 6 | import org.apache.ibatis.annotations.Insert; 7 | import org.apache.ibatis.annotations.InsertProvider; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | @Repository 15 | public interface GradeMapper extends CommonMapper { 16 | 17 | 18 | /** 19 | * 最原始批量增加 20 | */ 21 | @Insert("") 25 | void addAll(List grades); 26 | 27 | /** 28 | * 使用Provider批量增加 29 | */ 30 | @InsertProvider(type = Provider.class,method = "batchInsert") 31 | void addAll1(List list); 32 | 33 | /** 34 | * 使用内部类作为Provider 35 | */ 36 | class Provider{ 37 | /** 38 | * 返回String作为sql语句 39 | * 不使用SQL构建器 40 | * 此处的sql是原生sql 41 | * 42 | * 参数: map中存储了MyBatisMapper方法中的参数; 43 | * 如果方法只有一个参数,也可以直接写相同类型的参数直接接收; 44 | * 如果方法使用了@Param注解,则使用map用@Param的value作为key接收 45 | * 如果多个参数,且未使用@Param注解,则使用map,用索引作为key接收 46 | * 具体可以下断点自行查看map 47 | */ 48 | public String batchInsert(Map map) { 49 | List list = (List) map.get("list"); 50 | StringBuilder result = new StringBuilder(); 51 | result.append("INSERT INTO grade(name) VALUES"); 52 | list.stream().forEach(item ->{ 53 | result.append("(").append("\"" + item.getName() + "\"").append(")"); 54 | result.append(","); 55 | }); 56 | result.deleteCharAt(result.length()-1); 57 | return result.toString(); 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/dao/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.dao; 2 | 3 | import com.zx.springmybatis.config.mybatis.CommonMapper; 4 | import com.zx.springmybatis.dao.driver.CommonConditionLanguageDriver; 5 | import com.zx.springmybatis.entity.User; 6 | import org.apache.ibatis.annotations.*; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * author:Administrator 13 | * datetime:2017/11/7 0007 10:12 14 | * 用户mapper 15 | */ 16 | @Repository 17 | public interface UserMapper extends CommonMapper{ 18 | // @Select("select * from user") 19 | // List getAll(); 20 | 21 | User a(Long id); 22 | 23 | /** 24 | * 使用LanguageDriver进行通用的条件查询 25 | */ 26 | @Lang(CommonConditionLanguageDriver.class) 27 | @Select("select * from user") 28 | List findByCondition(User user); 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/dao/driver/CommonConditionLanguageDriver.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.dao.driver; 2 | 3 | import com.google.common.base.CaseFormat; 4 | import org.apache.ibatis.mapping.SqlSource; 5 | import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; 6 | import org.apache.ibatis.session.Configuration; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | /** 11 | * author:ZhengXing 12 | * datetime:2017/11/28 0028 14:19 13 | * 通用条件查询语言驱动 14 | */ 15 | public class CommonConditionLanguageDriver extends XMLLanguageDriver{ 16 | /** 17 | * 重写父类方法,以便在条件查询时,将不为空属性,加入where条件, 18 | * 例如: 19 | * select * from user 20 | * 21 | * and username=#{username} 22 | * and password=#{password} 23 | * 24 | * parameterType:mapper中方法接收的参数,如果参数有多个,其值为map,当参数为多个时,无法获悉每个参数的类型(应该是) 25 | */ 26 | @Override 27 | public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { 28 | //追加where 29 | StringBuilder sql = new StringBuilder().append(""); 30 | //默认将该参数类型作为实体类类型处理,获取所有属性 31 | Field[] fields = parameterType.getDeclaredFields(); 32 | 33 | //遍历实体类的每个属性 34 | for (Field field : fields) { 35 | //将java中 userId形式的属性转换为数据库中 user_id形式的 36 | String sqlField = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()); 37 | //循环增加的语句 38 | String temp = "and sqlField=#{javaField} "; 39 | //将字符串中的自定义标识字符:javaField和sqlField替换 40 | temp = temp.replaceAll("javaField",field.getName()) 41 | .replaceAll("sqlField", sqlField); 42 | sql.append(temp); 43 | } 44 | sql.append(""); 45 | 46 | //增加"; 48 | //继续执行父类的方法实现,构建SqlSource 49 | return super.createSqlSource(configuration, script, parameterType); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/dto/GradeDTO.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.dto; 2 | 3 | import com.zx.springmybatis.entity.Grade; 4 | import com.zx.springmybatis.entity.User; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.List; 12 | 13 | /** 14 | * author:ZhengXing 15 | * datetime:2017/11/29 0029 11:28 16 | * 班级信息和班级下所有学生 17 | */ 18 | @Data 19 | @Builder 20 | @AllArgsConstructor 21 | @Accessors(chain = true) 22 | public class GradeDTO implements Serializable { 23 | private Grade grade; 24 | private List users; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/entity/Grade.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.io.Serializable; 12 | import java.util.List; 13 | 14 | /** 15 | * author:ZhengXing 16 | * datetime:2017/11/28 0028 09:34 17 | * 班级 18 | */ 19 | @Data 20 | @Builder 21 | @Table 22 | @NoArgsConstructor 23 | @AllArgsConstructor 24 | public class Grade implements Serializable{ 25 | @Id 26 | @GeneratedValue(generator = "JDBC") 27 | private Long id; 28 | private String name; 29 | 30 | public Grade(String name) { 31 | this.name = name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import javax.persistence.Table; 12 | import java.io.Serializable; 13 | import java.util.List; 14 | 15 | /** 16 | * author:Administrator 17 | * datetime:2017/11/7 0007 09:10 18 | */ 19 | 20 | @Data 21 | @Builder 22 | @Table 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | @Accessors(chain = true) 26 | public class User implements Serializable{ 27 | @Id 28 | @GeneratedValue(generator = "JDBC") 29 | private Long id; 30 | private String name; 31 | private String password; 32 | private String loginAddress; 33 | private Long gradeId; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.service; 2 | 3 | import com.github.pagehelper.PageInfo; 4 | import com.zx.springmybatis.entity.User; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * author:Administrator 10 | * datetime:2017/11/7 0007 10:13 11 | */ 12 | public interface UserService { 13 | List getAll(); 14 | 15 | PageInfo getAllForPage(Integer pageNum, Integer pageSize); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/service/impl/CacheService.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.service.impl; 2 | 3 | import com.zx.springmybatis.dao.GradeMapper; 4 | import com.zx.springmybatis.dao.UserMapper; 5 | import com.zx.springmybatis.dto.GradeDTO; 6 | import com.zx.springmybatis.entity.Grade; 7 | import com.zx.springmybatis.entity.User; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.cache.annotation.*; 11 | import org.springframework.stereotype.Service; 12 | import tk.mybatis.mapper.entity.Example; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * author:ZhengXing 18 | * datetime:2017/11/29 0029 10:27 19 | * 20 | * 缓存测试类 21 | */ 22 | @Service 23 | @Slf4j 24 | @CacheConfig(cacheNames = "a") 25 | public class CacheService { 26 | @Autowired 27 | private UserMapper userMapper; 28 | 29 | @Autowired 30 | private GradeMapper gradeMapper; 31 | 32 | /** 33 | * 查询所有班级 34 | * 注意,@Cacheable中的cacheNames值需要在yml中配置,也就是spring.cache.cache-names 35 | * 如果需要指定一个特别的固定的key,需要使用单引号包裹 36 | */ 37 | @Cacheable 38 | @Caching 39 | public List findAll() { 40 | log.info("查询所有班级"); 41 | return gradeMapper.selectAll(); 42 | } 43 | 44 | /** 45 | * 修改某个班级 46 | * 修改班级后,清空findAll方法的缓存 47 | */ 48 | @CacheEvict(key = "'CacheService-findAll-'") 49 | public void updateGradeById(Long gradeId,Grade grade) { 50 | 51 | Example example = new Example(Grade.class); 52 | example.createCriteria().andEqualTo("id", gradeId); 53 | 54 | int i = gradeMapper.updateByExampleSelective(grade, example); 55 | //根据id直接更新 56 | //gradeMapper.updateByExampleSelective(); 57 | System.out.println("更新条数:" + i); 58 | } 59 | 60 | /** 61 | * 查询单个班级,根据id 62 | */ 63 | @Cacheable 64 | public Grade findOneByGradeId(Long gradeId) { 65 | return gradeMapper.selectByPrimaryKey(gradeId); 66 | } 67 | 68 | /** 69 | * 增加班级方法. 70 | * 插入时将其直接存入缓存; 71 | * 注意,这样就需要返回该对象整个 72 | * 关于id,此处已经自动回写了 73 | */ 74 | @CachePut(key = "'CacheService-findOneByGradeId-' + #result.id") 75 | public Grade insertGrade(Grade grade) { 76 | gradeMapper.insert(grade); 77 | return grade; 78 | } 79 | 80 | 81 | /** 82 | * 查询某个班级和班级下所有学生 83 | */ 84 | @Cacheable(cacheNames = "c") 85 | public GradeDTO findGradeForUsers(Long gradeId) { 86 | log.info("查询班级和其下所有学生"); 87 | Grade grade = gradeMapper.selectByPrimaryKey(gradeId); 88 | List users = userMapper.select(new User().setGradeId(gradeId)); 89 | return new GradeDTO(grade, users); 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/zx/springmybatis/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.service.impl; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.zx.springmybatis.dao.UserMapper; 6 | import com.zx.springmybatis.entity.User; 7 | import com.zx.springmybatis.service.UserService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * author:Administrator 16 | * datetime:2017/11/7 0007 10:14 17 | */ 18 | @Service 19 | @Slf4j 20 | public class UserServiceImpl implements UserService { 21 | @Autowired 22 | private UserMapper userMapper; 23 | 24 | @Override 25 | public List getAll() { 26 | return userMapper.selectAll(); 27 | } 28 | 29 | //分页测试 30 | @Override 31 | public PageInfo getAllForPage(Integer pageNum, Integer pageSize) { 32 | pageNum = pageNum == null ? 1 : pageNum; 33 | pageSize = pageSize == null ? 10 : pageSize; 34 | 35 | PageHelper.startPage(pageNum,pageSize); 36 | List userList = userMapper.selectAll(); 37 | PageInfo pageInfo = new PageInfo<>(userList); 38 | 39 | log.info("pageInfo:{}",pageInfo); 40 | return pageInfo; 41 | } 42 | 43 | 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 80 3 | spring: 4 | datasource: 5 | # 可自动识别 6 | driver-class-name: com.mysql.jdbc.Driver 7 | username: root 8 | password: 970389 9 | type: com.alibaba.druid.pool.DruidDataSource 10 | url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false 11 | # DataSource配置 12 | druid: 13 | # 初始容量 14 | initial-size: 10 15 | # 最大连接池个数 16 | max-active: 20 17 | # 最小空闲 18 | min-idle: 10 19 | # 获取连接最大等待时间 20 | max-wait: 3000 21 | # 是否缓存preparedStatement(PSCache),对游标提升巨大,建议oracle开启,mysql关闭 22 | pool-prepared-statements: false 23 | # 启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 24 | max-pool-prepared-statement-per-connection-size: 0 25 | # 检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 26 | validation-query: select 'x' 27 | # 检测连接是否有效的超时时间。秒,底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 28 | validation-query-timeout: 30 29 | # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 30 | test-on-borrow: false 31 | # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 32 | test-on-return: false 33 | # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 34 | test-while-idle: true 35 | # 驱逐策略间隔,如果连接空闲时间大于minEvictableIdleTimeMillis,则关闭 36 | time-between-eviction-runs-millis: 60000 37 | # 在池中的最小生存时间 38 | min-evictable-idle-time-millis: 30000 39 | # 在池中的最大生存时间 40 | max-evictable-idle-time-millis: 600000 41 | # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 42 | keep-alive: true 43 | # 连接初始化时,执行的sql 44 | connection-init-sqls: 45 | # 开启的过滤器,常用的有 监控统计:stat 日志:log4j 防御sql注入:wall 46 | filters: stat,wall,log4j 47 | # 合并多个dataSource的监控记录 48 | use-global-data-source-stat: true 49 | 50 | # 监控配置 51 | # 是否启用stat-filter默认值true 52 | web-stat-filter.enabled: true 53 | # 匹配的uri 54 | web-stat-filter.url-pattern: /* 55 | # 忽略的uri *.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* 56 | web-stat-filter.exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" 57 | # 是否启用session统计 58 | web-stat-filter.session-stat-enable: true 59 | web-stat-filter.session-stat-max-count: 1000 60 | web-stat-filter.principal-session-name: zx 61 | web-stat-filter.principal-cookie-name: zx 62 | # 监控单个url调用的sql列表。 63 | web-stat-filter.profile-enable: true 64 | # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置 65 | #是否启用监控界面默认值true 66 | stat-view-servlet.enabled: true 67 | # web.xml的url-pattern,也就是访问/druid/*访问到该servlet 68 | stat-view-servlet.url-pattern: /druid/* 69 | # 允许清空统计数据 70 | stat-view-servlet.reset-enable: true 71 | # 用户名 72 | stat-view-servlet.login-username: zx 73 | # 密码 74 | stat-view-servlet.login-password: 123456 75 | # ip白名单 76 | stat-view-servlet.allow: 77 | # ip黑名单 78 | stat-view-servlet.deny: 79 | 80 | # 过滤器配置 81 | filter: 82 | stat: 83 | # 聚合sql 开启慢sql查询 84 | merge-sql: true 85 | # 是否开启慢sql查询 86 | log-slow-sql: true 87 | # 超过多少时间为慢sql 开启慢sql查询 88 | slow-sql-millis: 1 89 | # 是否启用 90 | enabled: true 91 | # 安全配置,防止sql注入. 具体参数可查看文档,包括禁止各类增删查改的操作 92 | # wall: 93 | # config: 94 | 95 | #缓存 96 | cache: 97 | #缓存名字 98 | cache-names: #该属性的接收类型为list,得在这样写才可以分为一个个元素 99 | - a 100 | - b 101 | - c 102 | #缓存过期时间 103 | cacheExpires: #自定义属性,也是list,用来配置缓存过期时间,值为0时根据我的逻辑是使用默认超时时间的 104 | - 3600 105 | - 1 106 | - 0 107 | #缓存类型,同时引入guava包和redis时,不配置可能有bug 108 | type: redis 109 | #redis配置 110 | redis: 111 | host: 106.14.7.29 112 | port: 6379 113 | password: 970389 114 | pool: 115 | max-active: 10 116 | max-idle: 1 117 | min-idle: 0 118 | max-wait: 5000 119 | timeout: 5000 120 | database: 0 121 | output: 122 | ansi: 123 | enabled: always 124 | mybatis: 125 | configuration: 126 | # 驼峰 127 | map-underscore-to-camel-case: true 128 | mapper-locations: classpath:mapper/*.xml 129 | type-aliases-package: com.zx.springmybatis.entity 130 | 131 | # 输出MyBatis语句,trace会输出结果,debug只输出语句 132 | logging: 133 | level: 134 | com: 135 | zx: 136 | springmybatis: 137 | dao: debug 138 | 139 | -------------------------------------------------------------------------------- /src/main/resources/generator/config.properties: -------------------------------------------------------------------------------- 1 | # 数据库配置 2 | jdbc.driverClass = com.mysql.jdbc.Driver 3 | jdbc.url = jdbc:mysql://localhost:3306/sms-sender 4 | jdbc.user = root 5 | jdbc.password = 970389 6 | # 通用Mapper配置 7 | mapper.plugin = tk.mybatis.mapper.generator.MapperPlugin 8 | mapper.Mapper = com.zx.springmybatis.config.mybatis.CommonMapper 9 | -------------------------------------------------------------------------------- /src/main/resources/generator/generatorConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
-------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 13 | 14 | 15 | 16 | ${CONSOLE_LOG_PATTERN} 17 | utf8 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ERROR 26 | 27 | DENY 28 | 29 | ACCEPT 30 | 31 | 32 | 33 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n 34 | 35 | 36 | 37 | 38 | 39 | /log/smsSender/info.%d.log 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ERROR 48 | 49 | 50 | 51 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n 52 | 53 | 54 | 55 | 56 | 57 | /log/smsSender/error.%d.log 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/mapper/User.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/main/resources/sql/test1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Version : 50720 6 | Source Host : localhost:3306 7 | Source Database : test1 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50720 11 | File Encoding : 65001 12 | 13 | Date: 2017-12-01 18:01:14 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for grade 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `grade`; 22 | CREATE TABLE `grade` ( 23 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 24 | `name` varchar(255) DEFAULT NULL, 25 | PRIMARY KEY (`id`) 26 | ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4; 27 | 28 | -- ---------------------------- 29 | -- Table structure for test 30 | -- ---------------------------- 31 | DROP TABLE IF EXISTS `test`; 32 | CREATE TABLE `test` ( 33 | `id` bigint(20) NOT NULL, 34 | `name` varchar(255) DEFAULT NULL, 35 | `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, 36 | PRIMARY KEY (`id`) 37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 38 | 39 | -- ---------------------------- 40 | -- Table structure for user 41 | -- ---------------------------- 42 | DROP TABLE IF EXISTS `user`; 43 | CREATE TABLE `user` ( 44 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 45 | `name` varchar(255) DEFAULT NULL, 46 | `password` varchar(255) DEFAULT NULL, 47 | `login_address` varchar(255) DEFAULT NULL, 48 | `grade_id` bigint(20) DEFAULT NULL, 49 | PRIMARY KEY (`id`) 50 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4; 51 | -------------------------------------------------------------------------------- /src/test/java/com/zx/springmybatis/SpringMyBatisApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringMyBatisApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/zx/springmybatis/dao/UserMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.dao; 2 | 3 | import com.zx.springmybatis.entity.Grade; 4 | import com.zx.springmybatis.entity.User; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.ibatis.reflection.ArrayUtil; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.core.ValueOperations; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.util.CollectionUtils; 15 | import tk.mybatis.mapper.entity.Example; 16 | 17 | import java.util.Arrays; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | import static org.junit.Assert.*; 23 | 24 | /** 25 | * author:ZhengXing 26 | * datetime:2017/11/16 0016 15:58 27 | */ 28 | @SpringBootTest 29 | @RunWith(SpringRunner.class) 30 | @Slf4j 31 | public class UserMapperTest { 32 | 33 | @Autowired 34 | private UserMapper userMapper; 35 | 36 | @Autowired 37 | private GradeMapper gradeMapper; 38 | 39 | @Test 40 | public void test() { 41 | User temp = User.builder().id(1L).build(); 42 | 43 | Example example = new Example(User.class)//传入实体类对象构造 44 | .selectProperties("id", "name")//设置要查询的字段 45 | .excludeProperties("id");//设置不查询的字段,与要查询同时设置,要查询的优先 46 | example.orderBy("id").desc();//排序 47 | example.createCriteria();//其他方法类似,基本都能用方法名理解 48 | // .andLessThan("id","4");//查询属性小于该值的记录 49 | // .andGreaterThan("id","4");//查询属性大于该值的记录 50 | // .andAllEqualTo(temp);//查询字段值等于该对象的属性值的记录,所有属性。 51 | // .andEqualTo(temp);//查询字段值等于该对象的属性值的记录,非空属性。 52 | // .andBetween("name","a","c");//between查询 53 | // .andCondition("name = 'a' or name ='b'");//可以直接使用sql查询,此处输入where后面的字符 54 | 55 | List userList = userMapper.selectByExample(example); 56 | userList.forEach(user -> { 57 | log.info("user:{}", user); 58 | }); 59 | } 60 | 61 | @Test 62 | public void test1() { 63 | List grades = Arrays.asList(new Grade("a"), new Grade("b"), new Grade("c")); 64 | gradeMapper.addAll(grades); 65 | } 66 | 67 | @Test 68 | public void test2() { 69 | 70 | User user = new User().setName("a").setPassword("aa"); 71 | List a = userMapper.findByCondition(user); 72 | a.forEach(item -> System.out.println(a)); 73 | } 74 | 75 | @Test 76 | public void test3() { 77 | User a = userMapper.a(1L); 78 | System.out.println(a); 79 | } 80 | 81 | /** 82 | * 测试redis 83 | */ 84 | @Autowired 85 | private RedisTemplate redisTemplate; 86 | 87 | @Test 88 | public void test4(){ 89 | redisTemplate.opsForValue().set("a","test"); 90 | } 91 | 92 | 93 | } -------------------------------------------------------------------------------- /src/test/java/com/zx/springmybatis/service/impl/CacheServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.zx.springmybatis.service.impl; 2 | 3 | import com.sun.xml.internal.ws.api.model.CheckedException; 4 | import com.zx.springmybatis.dto.GradeDTO; 5 | import com.zx.springmybatis.entity.Grade; 6 | import com.zx.springmybatis.entity.User; 7 | import lombok.NonNull; 8 | import lombok.SneakyThrows; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | import java.util.List; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * author:ZhengXing 22 | * datetime:2017/11/29 0029 10:51 23 | * 缓存测试 24 | */ 25 | @SpringBootTest 26 | @RunWith(SpringRunner.class) 27 | @Slf4j 28 | public class CacheServiceTest { 29 | @Autowired 30 | private CacheService cacheService; 31 | 32 | @Test 33 | public void test1() { 34 | log.info("第一次查询"); 35 | findAll(); 36 | log.info("第二次查询"); 37 | findAll(); 38 | } 39 | 40 | @Test 41 | @SneakyThrows(InterruptedException.class)//lombok 消除受检异常 42 | public void test2() { 43 | log.info("第一次查询"); 44 | findGradeForUsers(); 45 | TimeUnit.SECONDS.sleep(2); 46 | log.info("第二次查询"); 47 | findGradeForUsers(); 48 | } 49 | 50 | 51 | @Test 52 | public void test3() { 53 | findAll(); 54 | findAll(); 55 | 56 | Grade grade = Grade.builder() 57 | .name("aaaaa").build(); 58 | cacheService.updateGradeById(1L, grade); 59 | 60 | // findAll(); 61 | } 62 | 63 | @Test 64 | public void test() { 65 | Grade grade1 = cacheService.insertGrade(Grade.builder() 66 | .name("xxxxxxxx").build()); 67 | System.out.println("新增成功:" + grade1); 68 | Grade grade = cacheService.findOneByGradeId(grade1.getId()); 69 | System.out.println(grade); 70 | } 71 | 72 | private void findAll() { 73 | List grades = cacheService.findAll(); 74 | grades.forEach(item -> { 75 | System.out.println(grades); 76 | }); 77 | } 78 | 79 | private void findGradeForUsers() { 80 | GradeDTO gradeDTO = cacheService.findGradeForUsers(1L); 81 | System.out.println(gradeDTO); 82 | } 83 | 84 | 85 | } --------------------------------------------------------------------------------