目录
一、概要分析
在日常敲代码的过程中,代码繁多以及运行速度慢,为了提高代码运行速度,优化代码性能,将提供一下两种方法来解决
二、集成连接池durid
一、连接池分类
C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
Proxool:是一个Java SQL Driver驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
Jakarta DBCP:DBCP是一个依赖Jakartacommons-pool对象池机制的数据库连接池。DBCP可以直接的在应用程序中使用。
BoneCP:是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据库。比C3P0/DBCP连接池速度快25倍。
Druid:Druid不仅是一个数据库连接池,还包含一个ProxyDriver(代理程序)、一系列内置的JDBC组件库、一个SQLParser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等(Druid是一个关系型数据库连接池,是阿里巴巴的一个开源项目,Druid在监控,可扩展性,稳定性和性能方面具有比较明显的优势.通过Druid提供的监控功能,可以实时观察数据库连接池和SQL查询的工作情况.使用Druid在一定程度上可以提高数据库的访问技能.)
二、集成步骤
1、新建模块
2、导入pom依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version></dependency>
3、更改yml文件
server:
port: 8080
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/crm?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5 # 初始化大小
min-idle: 10 # 最小连接数
max-active: 20 # 最大连接数
max-wait: 60000 # 获取连接时的最大等待时间
min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间,单位是毫秒
time-between-eviction-runs-millis: 60000 # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
filters: stat # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计 ,wall
validation-query: SELECT 1 # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
test-on-borrow: true # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
test-on-return: true # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
test-while-idle: true # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
stat-view-servlet:
enabled: true # 是否开启 StatViewServlet
allow: 127.0.0.1 # 访问监控页面 白名单,默认127.0.0.1
deny: 192.168.56.1 # 访问监控页面 黑名单
login-username: admin # 访问监控页面 登陆账号
login-password: 123 # 访问监控页面 登陆密码
filter:
stat:
enabled: true # 是否开启 FilterStat,默认true
log-slow-sql: true # 是否开启 慢SQL 记录,默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000,单位:毫秒
merge-sql: false # 合并多个连接池的监控数据,默认false
redis:
database: 0 #数据库索引
host: 127.20.10.5 #主机位置
port: 6379 #端口
password: 123456 #密码
jedis:
pool:
max-active: 8 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没限制)
max-idle: 8 #最大空闲
min-idle: 0 #最小空闲
timeout: 10000 #连接超时时间
logging:
level:
com.zxy.code.mapper: debug
4、写实体类
package com.zxy.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 学生表 student
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Student {
/**
* 学生编号 主键 自增
*/
private Long stuId;
/**
* 学生名字
*/
private String stuName;
/**
* 学生电话
*/
private String stuPhone;
/**
* 学生班级
*/
private Long stuClass;
/**
* 学生地址
*/
private String stuAddress;
}
5、mapper层
package com.zxy.code.mapper;
import com.zxy.code.pojo.Student;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;
//让当前类成为一个bean,被spring容器所管理
@Repository
public interface StudentMapper extends Mapper<Student> {
}
6、启动类进行管理mapper
Springboot05Application.java类:
package com.zxy.code;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.zxy.code.mapper")
//开启切面的自动代理
@EnableAspectJAutoProxy
//开启事务管理器
@EnableTransactionManagement
public class Springboot05Application {
public static void main(String[] args) {
SpringApplication.run(Springboot05Application.class, args);
}
}
7、service层
导入PageBean分页与PageAspect切面
PageBean分页:
package com.zxy.code.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private int total;
private int page = 1;
private int rows = 5;
private boolean pagination = true;
private String url;
private Map<String, String[]> ms;
public void setMs(Map<String, String[]> ms) {
this.ms = ms;
}
public int calcStartIndex() {
return (page - 1) * rows;
}
public int calcMaxPage() {
return total % rows == 0 ? total / rows : total / rows + 1;
}
public int nextPage() {
return page + 1 > calcMaxPage() ? calcMaxPage() : page + 1;
}
public int prevPage() {
return page - 1 > 1 ? page - 1 : 1;
}
public void setRequest(HttpServletRequest req) {
setUrl(req.getRequestURL().toString());
setMs(req.getParameterMap());
String page = req.getParameter("page");
if (page == null) {
setPage(1);
} else {
setPage(Integer.parseInt(page));
}
String rows = req.getParameter("rows");
if (rows == null) {
setRows(5);
} else {
setRows(Integer.parseInt(rows));
}
String pagination = req.getParameter("pagination");
if (pagination != null && "false".equals(pagination)) {
setPagination(false);
}
}
}
PageAspect切面:
package com.zxy.code.aspect;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zxy.code.util.PageBean;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
//让此类成为一个bean
@Component
@Aspect
@Slf4j
public class PageAspect {
@Around(value = "execution(* *..*Service.*Pager(..))")
public Object invoke(ProceedingJoinPoint point) throws Throwable {
PageBean pageBean = null;
for (Object e : point.getArgs()) {
if (e instanceof PageBean) {
pageBean = (PageBean) e;
break;
}
}
if (pageBean != null && pageBean.isPagination()) {
PageHelper.startPage(pageBean.getPage(), pageBean.getRows());
}
Object obj = point.proceed(point.getArgs());
if (obj != null) {
if (obj instanceof Page) {
Page page = (Page) obj;
PageInfo pageInfo = new PageInfo(page);
pageBean.setTotal(new Long(pageInfo.getTotal()).intValue());
return pageInfo.getList();
}
}
return obj;
}
}
接口StudentService.java:
package com.zxy.code.service;
import com.zxy.code.pojo.Student;
import com.zxy.code.util.PageBean;
import java.util.List;
public interface StudentService {
List<Student> findPager(PageBean pageBean);
}
实现接口StudentServiceImpl:
package com.zxy.code.service;
import com.zxy.code.mapper.StudentMapper;
import com.zxy.code.pojo.Student;
import com.zxy.code.util.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper mapper;
@Override
public List<Student> findPager(PageBean pageBean) {
return mapper.selectAll();
}
}
8、测试
Springboot05ApplicationTests :
package com.zxy.code;
import com.zxy.code.service.StudentService;
import com.zxy.code.util.PageBean;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot05ApplicationTests {
@Autowired
private StudentService service;
@Test
void contextLoads() {
PageBean pageBean=new PageBean();
service.findPager(pageBean).forEach(System.out::println);
}
}
效果
数据查询出来后,下一步如果想知道查询了那些语句,那么这里就需要用到数据源连接池监控“监控地址(http://localhost:8080/druid/login.html) ”这里就可以看见你查询那些语句,和执行时间多久。好处是使用Druid在一定程度上可以提高数据库的访问技能.,方便对数据进行整理。
分页插件yml
pagehelper : # https://pagehelper.github.io/docs/howtouse# 是否启用分页合理化。如果启用 , 当 page<1 时 , 会自动查询第一页的数据# 当 page>maxPages 时 , 自动查询最后一页数据# 不启用的 , 以上两种情况都会返回空数据reasonable : true# 分页插件会从查询方法的参数值中 , 自动根据上面 params 配置的字段中取值 , 查找到合适的值时就会自动分页supportMethodsArguments : true# 默认值为 false, 当该参数设置为 true 时 , 如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果page-size-zero : true# 指定数据库 , 不指定的话会默认自动检测数据库类型helper-dialect : mysq
三、集成集成缓存redis
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、更改yml文件
redis:
database: 0 #数据库索引
host: #主机位置
port: #端口
password: #密码
jedis:
pool:
max-active: 8 #最大连接数
max-wait: -1 #最大阻塞等待时间(负数表示没限制)
max-idle: 8 #最大空闲
min-idle: 0 #最小空闲
timeout: 10000 #连接超时时间
3、controller层
package com.lv.code.controller;
import com.lv.code.service.StudentService;
import com.lv.code.util.PageBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
//输出json数据
@RestController
//访问路径
@RequestMapping("stu")
public class StudentController {
@Autowired
private StudentService service;
@GetMapping("/all")
public Object index(HttpServletRequest request){
PageBean pageBean=new PageBean();
pageBean.setRequest(request);
return service.findPager(pageBean);
}
}
4、写好配置类
CrossConfiguration:
package com.zxy.code.conf;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author zjjt
*/
@Configuration
public class CrossConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
/*可以跨域的路径*/
.addMapping("/**")
/*可以跨域的ip*/
.allowedOrigins("*")
/*可以跨域的方法*/
.allowedMethods("*")
/*设置预检请求多就失效*/
.maxAge(6000)
/*允许携带的头部*/
.allowedHeaders("*");
}
}
RedisConfiguration:
package com.zxy.code.conf;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* @author zjjt
*/
@Configuration
@EnableCaching //开启缓存
public class RedisConfiguration extends CachingConfigurerSupport {
//@Bean 相当于 @Component,只不过Bean可以写在方法上面,内在跟Component是一样的
@Bean
@Primary
CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> cacheName + ":-cache-:")
/*设置缓存过期时间*/
.entryTtl(Duration.ofHours(1))
/*禁用缓存空值,不缓存null校验*/
.disableCachingNullValues()
/*设置CacheManager的值序列化方式为json序列化,可使用加入@Class属性*/
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
));
/*使用RedisCacheConfiguration创建RedisCacheManager*/
RedisCacheManager manager = RedisCacheManager.builder(factory)
.cacheDefaults(cacheConfiguration)
.build();
return manager;
}
@Bean
@Primary
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer stringSerializer = new StringRedisSerializer();
/* key序列化 */
redisTemplate.setKeySerializer(stringSerializer);
/* value序列化 */
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
/* Hash key序列化 */
redisTemplate.setHashKeySerializer(stringSerializer);
/* Hash value序列化 */
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
@Primary
@Override
public KeyGenerator keyGenerator() {
return (Object target, Method method, Object... params) -> {
final int NO_PARAM_KEY = 0;
final int NULL_PARAM_KEY = 53;
StringBuilder key = new StringBuilder();
/* Class.Method: */
key.append(target.getClass().getSimpleName())
.append(".")
.append(method.getName())
.append(":");
if (params.length == 0) {
return key.append(NO_PARAM_KEY).toString();
}
int count = 0;
for (Object param : params) {
/* 参数之间用,进行分隔 */
if (0 != count) {
key.append(',');
}
if (param == null) {
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {
/*JavaBean一定要重写hashCode和equals*/
key.append(param.hashCode());
}
count++;
}
return key.toString();
};
}
}
cachemManager缓存管理器
redisTemplate直接用它操作redis里面的内容
keyGenerator 主键生成器
3.1 、实现类中使用@cacheable注解开启缓存
@Cacheable(cacheNames = "aa",cacheManager = "cacheManager") //用来声明方法是
public List<Student> findPager(PageBean pageBean){
return mapper.selectAll();
}
cacheNames是缓存的名字用来做分类的,查看缓存时方便,可以在类上进行注解,表示所有的方法的缓存名字都为指定名字
cacheManager是缓存管理器
第一次运行会出现sql语句,然后数据会进入redis缓存中
当修改cacheManager时
redis
3.2、keyGenerator方法的使用
redis:
key用来标识方法的结果,由键去找值,找不到进数据库,要去缓存用户信息,我们一般把用户的id作为键
spel表达式使用#取值
例如
@Override
@Cacheable(key ="#pageBean.page") //用来声明方法是
public List<Student> findPager(PageBean pageBean){
return mapper.selectAll();
}
cachePut用来更新缓存,用来做修改
cacheEvict用来删除缓存,通过键key
3.3、Template的使用
4、写好帮助类
RedisUtil:
package com.zxy.code.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
* <p>
* 声明: 此工具只简单包装了redisTemplate的大部分常用的api,没有包装redisTemplate所有的api
* 如果对此工具类中的功能不太满意,或对StringRedisTemplate提供的api不太满意,
* 那么可自行实现相应的{@link StringRedisTemplate}类中的对应execute方法,以达
* 到自己想要的效果; 至于如何实现,则可参考源码或{@link LockOps}中的方法
* <p>
* 注: 此工具类依赖spring-boot-starter-data-redis类库
* 注: 更多javadoc细节,可详见{@link RedisOperations}
* <p>
* 统一说明一: 方法中的key、 value都不能为null
* 统一说明二: 不能跨数据类型进行操作,否者会操作失败/操作报错
* 如: 向一个String类型的做Hash操作,会失败/报错......等等
*/
@Slf4j
@Component
@SuppressWarnings("unused")
public class RedisUtil implements ApplicationContextAware {
/**
* 使用StringRedisTemplate(,其是RedisTemplate的定制化升级)
*/
private static StringRedisTemplate redisTemplate;
private static final ObjectMapper mapper = new ObjectMapper();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RedisUtil.redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
}
/**
* key相关操作
*/
public static class KeyOps {
/**
* 根据key,删除redis中的对应key-value
* <p>
* 注: 若删除失败,则返回false
* <p>
* 若redis中,不存在该key,那么返回的也是false
* 所以,不能因为返回了false,就认为redis中一定还存
* 在该key对应的key-value
*
* @param key 要删除的key
* @return 删除是否成功
*/
public static Boolean delete(String key) {
log.info("delete(...) => key -> {}", key);
// 返回值只可能为true/false,不可能为null
Boolean result = redisTemplate.delete(key);
log.info("delete(...) => result -> {}", result);
if (result == null) {
throw new RedisOpsResultIsNullException();
}
return result;
}
/**
* 根据keys,批量删除key-value
* <p>
* 注: 若redis中,不存在对应的key,那么计数不会加1,即:
* redis中存在的key-value里,有名为a1、a2的key,
* 删除时,传的集合是a1、a2、a3,那么返回结果为2
*
* @param keys 要删除的key集合
* @return 删除了的key-value个数
*/
public static long delete(Collection<String> keys) {
log.info("delete(...) => keys -> {}", keys);
Long count = redisTemplate.delete(keys);
log.info("delete(...) => count -> {}", count);
if (count == null) {
throw new RedisOpsResultIsNullException();
}
return count;
}
/**
* 将key对应的value值进行序列化,并返回序列化后的value值
* <p>
* 注: 若不存在对应的key,则返回null
* 注: dump时,并不会删除redis中的对应key-value
* 注: dump功能与restore相反
*
* @param key 要序列化的value的key
* @return 序列化后的value值
*/
public static byte[] dump(String key) {
log.info("dump(...) =>key -> {}", key);
byte[] result = redisTemplate.dump(key);
log.info("dump(...) =>