2.4 整合SpringBoot+Mybatis+Druid多数据源
9 Springboot整合SpringCache与Redis
1. Spring Boot 整合数据源
1.1. 数据初始化
打开mysql控制台,然后按如下步骤执行goods.sql文件:
第一步:登录mysql
mysql –uroot –proot
第二步:设置控制台编码方式
set names utf8;
第三步:执行goods.sql文件
source d:/goods.sql
1.2. 单数据源整合实现(重点掌握)
1.2.1. 使用默认的HikariCP
第一步:添加依赖。
编辑项目中pom.xml,右键项目的pom.xml文件,选择spring,如图-11所示:
图-11
查找mysql 驱动依赖,JDBC API依赖,如图-12所示:
图-12
依赖添加以后,在pom.xml文件中会自动添加如下两个依赖配置:
1. mysql数据库驱动依赖。
mysql mysql-connector-java runtime
2. spring对象jdbc支持。
org.springframework.boot spring-boot-starter-jdbc
第二步:配置连接池
打开application.properties配置文件,添加如下内容。
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=root
第三步:单元测试
@SpringBootTest public class DataSourceTests { @Autowired private DataSource dataSource; @Test public void testDataSource() throws Exception{ System.out.println(dataSource.getConnection()); } }
思考:市场说HiKariCP连接池性能相对较高,那么他的性能为什么好?在哪些方面做了优化设计?
1.2.2. 整合Druid对象
Druid是阿里巴巴推出一个为监控而生的开源连接池对象.在SpringBoot应用的使用步骤如下:
第一步:添加druid启动依赖(可以从mvnrepository.com站点去查找)
com.alibaba druid-spring-boot-starter 1.1.13
第二步:配置数据源
打开application.properties配置文件,修改连接池内容配置。
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql:///dbgoods?characterEncoding=utf-8&serverTimezone=GMT spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
第三步:简化写法,可以省略驱动
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql:///dbgoods?characterEncoding=utf-8&serverTimezone=GMT spring.datasource.username=root spring.datasource.password=root
第四步:单元测试
@RunWith(SpringRunner.class) @SpringBootTest public class DataSourceTests { @Autowired private DataSource dataSource; @Test public void testDataSource() throws Exception{ System.out.println(dataSource.getConnection()); } }
思考:为什么添加了druid依赖并进行了建议配置以后,再次获取数据源对象时,直接就是Druid对应的数据源对象呢?(与系统底层的自动配置有关)
1.3. 多数据源整合实现(扩展)
本小节作为课后扩展练习。
2. Spring Boot 整合MyBatis
2.1. 初始配置
2.1.1. 添加mybatis启动依赖
参考官网 mybatis.org/spring,找到springboot菜单选项.
org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.0
2.1.2. Mybatis简易配置
在spring boot的配置文件application.properties文件中添加如下内容
mybatis.configuration.default-statement-timeout=30 mybatis.configuration.map-underscore-to-camel-case=true logging.level.com.cy=DEBUG
其中,logging.level.com.cy=DEBUG 表示要对com.cy包中的DEBUG操作日志做输出。
2.2. 业务分析及实现
2.2.1. 基本业务实现及单元测试
定义商品业务数据层接口及业务方法。
package com.cy.pj.goods.dao; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; @Mapper public interface GoodsDao { @Delete("delete from tb_goods where id=#{id}") int deleteById(Integer id); }
其中:@Mapper是由MyBatis框架中定义的一个描述Mapper接口的的注解,用于告诉Spring框架此接口由底层(mybatis)创建其实现类对象,并存储到spring容器.
定义测试类
@SpringBootTest public class GoodsDaoTests { @Autowired private GoodsDao goodsDao; @Test public void testDeleteById() { int rows=goodsDao.deleteById(10); System.out.println("rows="+rows); } }
2.2.2. 拓展业务分析及实现
Step01:在GoodsDao中定义添加如下方法:
int deleteObjects(@Param("ids")Integer... ids);
说明:当接口方法对应的映射语句相对比较复杂时,建议将映射语句写到对应映射文件。
Step02:在src/main/resources目录下创建mapper/goods目录,然后在其目录添加
GoodsMapper.xml,并添加如下内容:
delete from tb_goods where id in #{id}
Step03:在application.properties文件中添加如下配置:
mybatis.mapper-locations=classpath:/mapper//.xml
Step04:在GoodsDaoTests类中添加如下单元测试方法进行单元测试:
@Test public void testDeleteObjects() { int rows=goodsDao.deleteObjects(17,18); System.out.println(rows); }
2.3. 构建业务层接口及实现类
参考图-13的设计,进行代码实现。(课堂练习)
图-13
2.4 整合SpringBoot+Mybatis+Druid多数据源
步骤一:
创建2个mybatisconfig配置类,装配数据源
1、MybatisConfigMaster
2、MybatisConfigSlave
3、配置druid数据源与事物管理器
步骤二:
在两个配置类上分别加上@MapperScan,并且指定各自扫描的Mapper包路径,注意:两个包路径不能重叠要分开,示例代码如下:
//数据源1对应的mybatis配置 //sqlSessionFactoryRef也要11对应,一个数据源对应一个sqlSessionFactory @MapperScan(value = {"com.zzxy.cy.starter.dao.ds1"},sqlSessionFactoryRef = "sqlSessionFactory1") @Configuration public class MybatisConfigMaster{ } //数据源2对应的mybatis配置 //sqlSessionFactoryRef也要11对应,一个数据源对应一个sqlSessionFactory @MapperScan(value = {"com.zzxy.cy.starter.dao.ds2"},sqlSessionFactoryRef = "sqlSessionFactory2") @Configuration public class MybatisConfigSlave { }
步骤三:
单独设置每个数据源对应的mybatis的mapper.xml文件路径,这个也必须分开不能重叠,通过代码里明确指定:
数据源1对应的mybatis的mapper.xml路径 factory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:/mapper/ds1/.xml")); 数据源2对应的mybatis的mapper.xml路径 factory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:/mapper/ds2/.xml"));
注意:一个mapper.xml对应一个mapper接口
数据源1对应的mapper.xml的内容 #namespace对应mapper接口全类名 delete from nb_user where id=#{id} 数据源2对应的mapper.xml的内容 delete from nb_user where id=#{id}
步骤四:
向service里注入我们的mapper接口的代理对象,示例代码如下:
@Service public class UserServiceImpl implements UserService{ //mybatis的mapper对象,操作数据源1 @Autowired private NbUserMapper nbUserMapper; //管理数据源1的事物管理器 @Transactional(transactionManager = "transactionManager1") public int createUser(NbUser user) { user.setGmtCreatetime(new Date()); return nbUserMapper.createUser(user); } //mybatis的mapper对象,操作数据源2 @Autowired private NbStudentMapper nbStudentMapper; //管理数据源2的事物管理器,必须配套使用 @Transactional(transactionManager = "transactionManager2") public int createStudent(NbStudent nbStudent){ return nbStudentMapper.createStudent(nbStudent); } }
步骤五:
编写controller,注入service,进行接口调用测试,代码不做详细说明,比较简单
3. Spring Boot 整合WEB应用
3.1.1. 添加项目依赖
编辑pom.xml文件,添加web依赖,Thymeleaf依赖,代码如下:
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf
其中: Spring Web Starter 提供Spring MVC 依赖支持,并会自动添加一个tomcat依赖,作为嵌入式web服务器使用.thymeleaf一个html模板引擎,提供了与Spring MVC进行整合的API,可作为MVC架构中Web应用的View层。
3.1.2. Spring Web简易配置
在application.properties文件中添加视图解析器配置。
spring.thymeleaf.prefix=classpath:/templates/pages/ spring.thymeleaf.suffix=.html
说明:要基于配置在src/main/resources目录下创建templates/pages目录
3.2. 业务分析及实现
编写GoodsController类并将其交给spring管理。
package com.cy.pj.goods.controller; @Controller @RequestMapping("/goods/") public class GoodsController { @Autowired public GoodsService goodsService; @RequestMapping("doDeleteGoods") @ResponseBody public String doDeleteObjects() { int rows=goodsService.deleteObjects(); return "delete ok,rows="+rows; } @RequestMapping("doGoodsUI") public String doGoodsUI() { return "goods"; } } }
说明:需要在/templates/pages目录下创建goods.html,然后启动项目进行访问测试实现。
课堂练习:基于图-14的设计,完成商品库中数据的删除操作.
图-14
4.工程打包
4.1 引入打包Plugin
org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 org.apache.maven.plugins maven-jar-plugin true false lib/ com.zzxy.cy.starter.SpringbootStarterPrimaryApplication
5、Springboot-集成PageHelper
PageHelper是专门针对mybatis的分页插件,集成PageHelper的前提条件是先集成Mybatis
1、第一步:引入依赖包
com.github.pagehelper pagehelper-spring-boot-starter 1.2.132、PageHelper调用
//启动分页 //pageNow,要去的目标页 //pageSize,一页显示的记录数 PageHelper.startPage(pageNow,pageSize); //SystemLog 实体对象 Page systemLogs = (Page)sysLogMapper.selectPageList();
上面的第4行代码必须要在写在Mybatis的Mapper接口调用之前(即第6行),Page是PageHelper插件里一个继承了List的类,里面包含总记录数,当前页等等重要信息。但是这个类被转成json不会包含分页的详细信息,如果需要包含分页的详情信息,需要再用PageInfo包装,如下代码所示:
PageInfo pageInfo = new PageInfo(systemLogs);
至此,分页调用完毕!
6、SpringBoot整合Swagger
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是服务于前后端分离,减少前后端开发人员沟通交流成本,提高开发效率(为了甩锅)。
6.1 引入Swagger相关依赖
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2
6.2 Swagger装配
进行springboot与swagger集成的配置,配置如下:
@EnableSwagger2 @Configuration public class SwaggerConfig { /** * 创建Rest(轻量级的http协议)风格的API服务 * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() //为当前包路径 .apis(RequestHandlerSelectors.basePackage("com.tg.mall.admin.controller")) .paths(PathSelectors.any()) .build(); } //构建 api文档的详细信息函数,注意这里的注解引用的是哪个 private ApiInfo apiInfo() { return new ApiInfoBuilder() //页面标题 .title("Spring Boot 使用 Swagger2 构建RESTful API") //创建人 .contact(new Contact("杨过", "baidu.com/", "XXXXX@qq.com")) //版本号 .version("1.0") //描述 .description("天狗商城-API-描述") .build(); } }
7.热部署
项目启用热部署后,修改代码不需要再重启项目即可生效,生效时间会有一定延迟
7.1 引入依赖
org.springframework.boot spring-boot-devtools true true7.2 插件配置
org.springframework.boot spring-boot-maven-plugin 2.3.7.RELEASE com.zz.springboot.thymeleaf.SpringbootThymeleafApplication true true
7.3 开启运行加载配置
按住快捷键 ctrl+shift+alt+/ 打开面板
点击registry打开下方面板,勾选
7.4 允许工程自动构建
- springboot整合属性校验组件
springboot整合spring-boot-starter-validation,该组件可以帮助我们自动校验表单提交的数据是否符合我们的要求,具体使用流程如下。
8.1 引入依赖
org.springframework.boot spring-boot-starter-validation compile8.2 代码示例
@RestController @RequestMapping("/sys/user") public class SysUserController { @PostMapping("/add") public Result add(@Valid @RequestBody SysUser sysUser){ } } @Validated @NoArgsConstructor @Data public class SysUser implements Serializable { //@Pattern(regexp = "",message = "密码不符合要求") @Length(max = 15,min = 3,message = "密码长度不符合规范") @NotBlank(message = "密码不能为空") private String password; /** * 邮箱 / @Pattern(regexp = "^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$", message = "邮箱填写不正确") private String email; } 当属性校验不符合规范时,程序将抛出异常 异常将被兜底异常处理机制捕捉,代码如下 /* * 控制器接收参数校验异常捕捉器 / @ExceptionHandler({MethodArgumentNotValidException.class}) @ResponseBody public Result argsValidErrorHandle(MethodArgumentNotValidException e){ log.error("异常:{}",e); BindingResult bindingResult = e.getBindingResult(); /* * 获取所有校验不通过的字段的错误信息对象 / List fieldErrors = bindingResult.getFieldErrors(); StringBuilder error = new StringBuilder(); /* * 拼接错误信息提示:"邮箱填写不正确,手机号不能为空," */ fieldErrors.stream().forEach(field-> error.append(field.getDefaultMessage()).append(",")); String message = error.toString(); return Result.failure(message.substring(0,message.length()-1),null); }
9 Springboot整合SpringCache与Redis
9.1 简介
springboot项目当中经常会需要用到缓存,缓存有很多种实现方式,比如springboot支持的换有以下几种:
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
如果没有特别声明,springboot会按照顺序从上到下查找,顺序越靠前的越被优先使用。这里我们推荐使用redis,缓存数据可以被多个应用节点共享。
9.2 导入依赖
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 2.4.3 compile org.springframework.boot spring-boot-starter-cache
9.3 yml配置
spring: application: name: springboot-redis-cache redis: # redis服务的ip地址 host: 192.168.159.130 #服务端口号 port: 6379 #密码 password: #选择使用的数据库 database: 0 lettuce: #连接池 pool: # 连接池允许的最大连接数 max-active: 30 # 最大空闲连接数 max-idle: 15 # 最小空闲连接数,默认为0 min-idle: 10 # 允许等待获取连接的最大时间,超时自动抛异常 max-wait: 15s cache: # 缓存类型使用redis type: redis redis: # 是否启用key前缀 use-key-prefix: true # 是否缓存null值 cache-null-values: false # 公共全局缓存key前缀 key-prefix: cache
9.4 开启缓存功能
/** * 开启缓存框架功能 */ @EnableCaching @SpringBootApplication public class SpringbootRedisCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootRedisCacheApplication.class, args); } }
9.5 装配缓存管理器与序列化器
/** * redis缓存管理配置 * @author :杨过 * @date :Created in 2022/12/7 / @Configuration @EnableConfigurationProperties({CacheProperties.class}) public class RedisCacheConfig { private final String separator = ":"; / * 装配自定义的jackson序列化器 */ @Bean public RedisSerializer jackson2JsonRedisSerializer(){ //todo 1.自定义值序列化器 Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); //todo 2.jackson工具类 ObjectMapper objectMapper = new ObjectMapper(); //递推子类的属性也序列化成json objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //声明-对象类型与对象数据都将序列化到redis当中 objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL); //忽略对象中为null的字段,不进行序列化 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //todo 3.绑定序列化器与jackson工具 serializer.setObjectMapper(objectMapper); return serializer; } @Bean public CacheManager cacheManager( CacheProperties cacheProperties, RedisConnectionFactory factory, RedisSerializer jackson2JsonRedisSerializer) { CacheProperties.Redis redisProperties = cacheProperties.getRedis(); // 以锁写入的方式创建RedisCacheWriter对象 RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory); //设置序列化器,默认的序列化器时jdk序列化器可读性差,换成jackson序列化器 RedisSerializationContext.SerializationPair pair = RedisSerializationContext .SerializationPair .fromSerializer(jackson2JsonRedisSerializer); // 创建默认缓存配置对象、 将@Cacheable缓存key值时默认会给value或cacheNames后加上双冒号 改为 单冒号 RedisCacheConfiguration config = RedisCacheConfiguration .defaultCacheConfig() .serializeValuesWith(pair) .computePrefixWith(name -> name + separator); //设置yml当中配置到缓存管理器当中 //设置缓存过期时间 if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } //设置全局缓存前缀 if (redisProperties.getKeyPrefix() != null) { config = config.computePrefixWith( name -> { String prefix = redisProperties.getKeyPrefix(); Assert.notNull(prefix, "Prefix must not be null!"); return prefix+separator+name+separator; }); } //null值是否缓存 if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } //是否启用全局前缀 if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } RedisCacheManager cacheManager = new RedisCacheManager(writer, config); return cacheManager; } }
9.6 缓存应用示例
/** * @author :杨过 * @date :Created in 2022/12/6 / @Slf4j @RestController public class StudentController { @Autowired private StudentMapper studentMapper; / * 1.如果有缓存则直接读取缓存 * 2.如果没有缓存,则执行方法,并且将方法返回结果对象缓存 / @Cacheable( //缓存名称前缀 value = "student", //获取指定参数当中的值 key = "#id" ) @GetMapping("/detail/{id}") public Student1 detail(@PathVariable("id") Integer id){ //todo 2. 查询数据库 Student1 result = studentMapper.selectById(id); return result; } @Cacheable( //缓存名称前缀 value = "student", //获取指定对象当中的字段值 key = "#student1.id" ) @PostMapping("/detail") public Student1 detail(Student1 student1){ //todo 2. 查询数据库 Student1 result = studentMapper.selectById(student1.getId()); return result; } /* * 无论在缓存中是否存在缓存,detail方法都会执行, * 并将查到的结果添加指定的缓存中 / @CachePut(value = "student", key = "#id") @GetMapping("/detail1/{id}") public Student1 detail1(@PathVariable("id") Integer id){ //todo 2. 查询数据库 Student1 result = studentMapper.selectById(id); return result; } /* * 当执行当前方法调用时,将触发删除指定缓存 */ @CacheEvict(value = "student", key = "#id", //为true,则为清空所有缓存(注意只会清缓存) allEntries = true ) @GetMapping("/delete/{id}") public String delete(@PathVariable("id") Integer id){ return "delete success"; } }
10.springboot整合redisson框架
10.1 简介
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
10.2 整合步骤
10.2.1 引入依赖
版本匹配说明:因为redisson框架的版本与springboot版本有严格的适配要求,如果版本对不上可能会导致项目出错,版本适配要求可参照下方链接所示:
redisson-spring-data-{version}是redisson-spring-boot-starter的子依赖
org.springframework.boot spring-boot-starter-data-redis 2.3.7.RELEASE org.apache.commons commons-pool2 2.4.3 compile org.redisson redisson-spring-boot-starter 3.14.110.2.2 配置yml
配置application.yml
spring: application: name: springboot-lock redis: # redis服务的ip地址 host: 192.168.159.130 #服务端口号 port: 6379 #密码 password: #选择使用的数据库 database: 0 lettuce: #连接池 pool: # 连接池允许的最大连接数 max-active: 30 # 最大空闲连接数 max-idle: 15 # 最小空闲连接数,默认为0 min-idle: 10 # 允许等待获取连接的最大时间,超时自动抛异常 max-wait: 15s redisson: #配置单点模式,告诉redisson配置文件的位置 file: classpath:redisson.yml
配置redisson单节点:
单节点配置 singleServerConfig: # 连接空闲超时,单位:毫秒 idleConnectionTimeout: 10000 # 连接超时,单位:毫秒 connectTimeout: 10000 # 命令等待超时,单位:毫秒 timeout: 3000 # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。 # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。 retryAttempts: 3 # 命令重试发送时间间隔,单位:毫秒 retryInterval: 1500 # # 重新连接时间间隔,单位:毫秒 # reconnectionTimeout: 3000 # # 执行失败最大次数 # failedAttempts: 3 # 密码 password: # 单个连接最大订阅数量 subscriptionsPerConnection: 5 # 客户端名称 clientName: redisson # # redis服务节点地址 address: redis://192.168.159.130:6379 # 发布和订阅连接的最小空闲连接数 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 subscriptionConnectionPoolSize: 50 # 最小空闲连接数 connectionMinimumIdleSize: 32 # 连接池大小 connectionPoolSize: 64 # 数据库编号 database: 1 # DNS监测时间间隔,单位:毫秒 dnsMonitoringInterval: 5000 # 线程池数量,默认值: 当前处理核数量 * 2 threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 nettyThreads: 0 # 序列化编码 codec: !<org.redisson.codec.JsonJacksonCodec> {} # 传输模式 transportMode : "NIO"
10.2.3 注入客户端使用
redisson框架为我们实现了自动装配,不需要写任何代码即可使用,示例:
使用redisson做分布式锁。
@Slf4j @RestController public class RedissonLockController { /** * redisson框架客户端 / @Autowired private RedissonClient client; @GetMapping("/deduck") public String update(Integer id){ log.info("running..."); /* * 获取非公平锁,锁的name是redis的key / RLock lock = client.getLock("redisson:lock:" + id); boolean isLocked = false; try { /* * 加锁: * waitTime: 加锁失败的线程允许的最大等待时间,超过 * 该时间则返回加锁失败。注意:测试时一定要使用不同浏览器 * leaseTime: 当值为-1时表示将开启看门狗机制,周期性自动续命, * 续命周期为每(waitTime/3)间隔时间执行一次,不等于-1时即表示 * 锁的过期时间,超过该时间锁自动释放,示例: * leaseTime=10,表示10秒后锁自动释放 * return:方法返回值为boolean,为true表示加锁成功,false为失败 / isLocked = lock.tryLock(30, -1, TimeUnit.SECONDS); if(isLocked){ //加锁成功的额逻辑 }else{ //加锁失败的逻辑 } } catch (Exception e) { e.printStackTrace(); } finally { /* * 只有加锁成功的线程才可以释放锁 */ if(isLocked){ lock.unlock(); } } return "当前排队人数较多,请稍后再试!"; } }