1.springboot整合mybatis
- pom文件添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<scope>test</scope>
<version>1.3.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
- application.yml配置文件添加mybatis的配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/redis?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&connectTimeout=3000&socketTimeout=1000
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
minIdle: 5
maxActive: 100
initialSize: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
removeAbandoned: true
filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
druidLoginName: anyUser # 登录druid的账号
druidPassword: anyPassword # 登录druid的密码
cachePrepStmts: true # 开启二级缓存
mybatis:
typeAliasesPackage: hecr.test.mapper
mapperLocations: classpath:/hecr/test/mapper/*.xml
mapperScanPackage: hecr.test.mapper
configLocation: classpath:/mybatis-config.xml
- resources目录下添加mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 使全局的映射器启用或禁用缓存。 -->
<setting name="cacheEnabled" value="true" />
<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
<setting name="aggressiveLazyLoading" value="true"/>
<!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true -->
<setting name="multipleResultSetsEnabled" value="true" />
<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
<setting name="useColumnLabel" value="true" />
<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false -->
<setting name="useGeneratedKeys" value="false" />
<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 -->
<setting name="autoMappingBehavior" value="PARTIAL" />
<!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) -->
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="25" />
<setting name="defaultFetchSize" value="100" />
<setting name="safeRowBoundsEnabled" value="false" />
<!-- 使用驼峰命名法转换字段。 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
<setting name="localCacheScope" value="SESSION" />
<!-- 默认为OTHER,为了解决oracle插入null报错的问题要设置为NULL -->
<setting name="jdbcTypeForNull" value="NULL" />
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
</settings>
</configuration>
- 添加druid的配置类
package hecr.test.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan("hecr.test.mapper.mapper")
public class DataSourceConfig {
private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.type}")
private String dbType;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druidLoginName}")
private String druidLoginName;
@Value("${spring.datasource.druidPassword}")
private String druidPassword;
@Bean(name = "dataSource", destroyMethod = "close", initMethod = "init")
@Primary //不要漏了这
public DataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
try {
datasource.setUrl(this.dbUrl);
datasource.setDbType(dbType);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setFilters(filters);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}
/ 下面是druid 监控访问的设置 /
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*"); //url 匹配
reg.addInitParameter("allow", "192.168.0.111,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
reg.addInitParameter("deny", "192.168.0.112"); //IP黑名单 (存在共同时,deny优先于allow)
reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
return reg;
}
@Bean(name = "druidWebStatFilter")
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
return filterRegistrationBean;
}
}
2.添加Spring-cache作为缓存
// 因为必须要有返回值,才能保存到数据库中,如果保存的对象的某些字段是需要数据库生成的,
//那保存对象进数据库的时候,就没必要放到缓存了
@CachePut(key = "#p0.id") //#p0表示第一个参数
//必须要有返回值,否则没数据放到缓存中
public User insertUser(User u) {
this.userMapper.insert(u);
//u对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
return this.userMapper.find(u.getId());
}
@CachePut(key = "#p0.id")
public User updateUser(User u) {
this.userMapper.update(u);
//可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
return this.userMapper.find(u.getId());
}
@Nullable
@Cacheable(key = "#p0") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
public User findById(String id) {
System.err.println("根据id=" + id + "获取用户对象,从数据库中获取");
Assert.notNull(id, "id不用为空");
return this.userMapper.find(id);
}
@CacheEvict(key = "#p0") //删除缓存名称为userInfoCache,key等于指定的id对应的缓存
public void deleteById(String id) {
this.userMapper.delete(id);
}
//清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
//如果数据失败了,缓存时不会清除的
@CacheEvict(allEntries = true)
public void deleteAll() {
this.userMapper.deleteAll();
}
上述代码的缺点:
- key值不唯一
- 序列化问题
- 没有过期时间
解决方案:
- 在redis的配置类中添加相关bean
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(100));
redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
- 在service层的实现方法如下:
@Nullable
@Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
public User findByIdTtl(String id) {
System.err.println("根据id=" + id + "获取用户对象,从数据库中获取");
Assert.notNull(id, "id不用为空");
return this.userMapper.find(id);
}
3.文章参考链接
a. https://blog.youkuaiyun.com/u012102536/article/details/123690114
b. https://www.jianshu.com/p/fd62bb126489