自定义创建Springboot项目
在创建时点击:Custom,输入:https://start.aliyun.com/ (阿里) http://start.springboot.io/
https://gitee.com/cqupt-wang-dahammer/yygh_parent.git
一、MyBatis-Plus实现数据库crud
1)、MyBatis-Plus是什么?
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2)、MyBatis-Plus入门案例:
创建数据库 mybatis_plus:
create database mybatis-plus
创建表:
CREATE TABLE USER
(
id BIGINT(20)NOT NULL COMMENT '主键ID',
NAME VARCHAR(30)NULL DEFAULT NULL COMMENT '姓名',
age INT(11)NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50)NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
插入数据:
INSERT INTO user (id, name, age, email)VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
创建springboot工程
导入相关依赖(统一使用springboot 2.2.1 版本)
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
创建实体类 User:
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
创建Mapper接口继承BaseMapper:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wyf.demomptest.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
配置文件:application.properties
# 应用名称
spring.application.name=demomptest
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=13527343138
主启动类:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.wyf.demomptest.mapper")
//由于BaseMapper是动态生成的对象,而动态生成的对象是默认找不到的,所以需要扫描进来
public class DemomptestApplication {
public static void main(String[] args) {
SpringApplication.run(DemomptestApplication.class, args);
}
}
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemomptestApplicationTests {
@Autowired
private UserMapper mapper;
//这里会爆红是因为UserMapper 找不到对象,因为UserMapper 是动态生成的
//在接口UserMapper上加一个注解@Repository即可
@Test
public void findAll(){
List<User> users = mapper.selectList(null);
for (User user:users){
System.out.println(user);
}
}
}
测试结果:
查看sql输出日志:在配置文件中加
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在此测试查看控制台输出
添加操作:
@Test
public void testAdd(){
User user=new User(null,"lucy",20,"123456@qq.com");
int insert = mapper.insert(user);
System.out.println(insert);
}
测试结果:
表中数据:
发现我们插入数据的时候并没有设置主键id,但是表中自己生成了主键id,这是由于MyBatis-Plus的主键策略。
MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)
@TableId(type = IdType.ASSIGN_ID)
private String id;
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
修改操作:
@Test
public void testUpdate(){
User user=new User(1457681848516091905L,"王大锤",22,"456798@qq.com");
int i = mapper.updateById(user);
System.out.println(i);
}
测试结果:
多个id批量查询
//多个id批量查询
@Test
public void testSelect(){
//selectBatchIds 里面放入集合
List<User> users = mapper.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user:users){
System.out.println(user);
}
}
测试结果:
简单条件查询
//简单条件查询
@Test
public void testSelect2(){
Map<String,Object> map=new HashMap<>();
map.put("name","Jack");
map.put("age",20);
List<User> users = mapper.selectByMap(map);
for (User user:users){
System.out.println(user);
}
}
测试结果:
删除操作:分为物理删除和逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
@Test
public void testDelById(){
int i = mapper.deleteById(1457690898662404097l);
System.out.println(i);
}
测试结果:
批量删除、简单删除和批量查询、简单查询类似
分页查询:
配置分页插件
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
编写分页代码:创建page对象,传入两个参数(当前页和每页记录数),然后调用MyBatis-Plus的方法实现分页
//分页查询
@Test
public void testSelectPage(){
Page<User> page=new Page<>(1,3);
Page<User> userPage = mapper.selectPage(page, null);
//返回对象得到分页所有数据
long pages = userPage.getPages();//总页数
System.out.println(pages);
long current = userPage.getCurrent();//当前页
System.out.println(current);
List<User> records = userPage.getRecords();//查询数据集合
System.out.println(records);
long total = userPage.getTotal();//总记录数
System.out.println(total);
boolean hasNext = userPage.hasNext();//是否有后一页
System.out.println(hasNext);
boolean hasPrevious = userPage.hasPrevious();//是否有前一页
System.out.println(hasPrevious);
}
测试结果:
3)、MyBatis-plus条件查询:常用的是QueryWrapper
ge(大于)、gt(大于等于)、le(小于)、lt(小于等于)、isNull、isNotNull:
//这里只演示ge,其他的一样
@Test
public void testSelect3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//第一个参数是表中的字段 第二个参数是需要查询的值
queryWrapper.ge("age",21);
List<User> users = mapper.selectList(queryWrapper);
for (User user:users){
System.out.println(user);
}
}
eq(等于)、ne(不等于)
@Test
public void testSelect3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//第一个参数是表中的字段 第二个参数是需要查询的值
queryWrapper.eq("name","王大锤");
List<User> users = mapper.selectList(queryWrapper);
System.out.println(users);
}
between、notBetween (包含大小边界)
@Test
public void testSelect3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//查询年龄在10到24之间的人
queryWrapper.between("age",10,24);
List<User> users = mapper.selectList(queryWrapper);
for (User user:users){
System.out.println(user);
}
}
like、notLike、likeLeft、likeRight
@Test
public void testSelect3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//模糊查询 查询名字中带有王的数据
queryWrapper.like("name","王");
List<User> users = mapper.selectList(queryWrapper);
for (User user:users){
System.out.println(user);
}
}
(排序)orderBy、orderByDesc、orderByAsc
@Test
public void testSelect3(){
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//排序 根据id降序排列
queryWrapper.orderByDesc("id");
List<User> users = mapper.selectList(queryWrapper);
for (User user:users){
System.out.println(user);
}
}
4)、MyBatis-Plus自动填充 表中类型为dateTime
准备工作:在表中添加两个datetime类型的新字段:
在表对应的实体类上添加对应的属性:
private Date createTime; //create_time
private Date updateTime; //update_time
在实体类要进行自动填充的属性加上注解,指定是添加的时候有值,还是修改的时候有值
@TableField(fill = FieldFill.INSERT)
private Date createTime; //create_time
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime; //update_time
创建一个类实现接口,实现接口的两个方法,一个方法添加执行,一个方法修改执行,设置添加什么值。
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//mp执行添加操作的时候,这个方法就执行了
@Override
public void insertFill(MetaObject metaObject) {
//三个值,第一个是添加时要执行的属性名字,第二个是对应的值,第三个是MetaObject对象
this.setFieldValByName("crateTime",new Date(),metaObject);
}
//mp执行修改操作的时候,这个方法就执行了
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
测试结果:
先加入一条数据
@Test
public void testAdd(){
User user=new User(null,"aaa",20,"74564564@qq.com");
user.setCreateTime(new Date());
int insert = mapper.insert(user);
System.out.println(insert);
}
然后修改刚刚插入的结果:
@Test
public void testUpdate(){
User user=new User(1458619659327074305l,"cqupt",22,"66666@qq.com");
int i = mapper.updateById(user);
System.out.println(i);
}
表中的数据:
5)、逻辑删除
逻辑删除,假删除:将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。
逻辑删除使用场景:可以进行数据恢复,有关联数据,不便删除
在数据库中添加字段 del 类型为 int
在实体类上加入属性
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer del;
在之前自定填充的方法上加入自动填充del的值
this.setFieldValByName("del",0,metaObject);
先添加一条记录:
@Test
public void testAdd(){
User user=new User(null,"bbb",20,"74564564@qq.com");
user.setCreateTime(new Date());
int insert = mapper.insert(user);
System.out.println(insert);
}
表中记录:
然后尝试删除这条记录:
@Test
public void testDelById(){
int i = mapper.deleteById(1458621441893670914l);
System.out.println(i);
}
发现sql语句是更新 而不是删除,同时讲del字段变为1,此时查看表中的数据,del字段变成1
application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
=
6)、乐观锁(?)
主要适用场景:
当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:
取出记录时,获取当前version。更新时,带上这个version,执行更新时, set version = newVersion where version = oldVersion,如果version不对,就更新失败,接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:
在表中添加字段作为版本号,在表对应实体类添加版本号属性
配置乐观锁的插件
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.wyf.demomptest.mapper") //这时候可以将主启动类中的MapperScan注解加到这里来
public class MpConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
=========================================================================
二、前后端分离概念
=========================================================================三、EasyExcel
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
EasyExcel写操作
第一步、引入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
第二步、创建实体类,必须和excel表中的数据相同,在对应的属性上面添加注解,设置表头内容
@Data
public class UserData {
@ExcelProperty("用户编号")
private int uid;
@ExcelProperty("用户名称")
private String username;
}
第三步、实现写操作的具体代码
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
public class TestWrite {
public static void main(String[] args) {
//构建数据的List集合
List<UserData> list=new ArrayList<>();
for (int i = 0; i < 10; i++) {
UserData userData=new UserData();
userData.setUid(i);
userData.setUsername("王大锤"+i+"号");
list.add(userData);
}
//设置excel文件路径和文件名称
String fileName="D:\\excelData\\01.xlsx";
//调用方法实现写操作 第一个参数 文件名,第二个参数 类型
EasyExcel.write(fileName,UserData.class).sheet("用户信息")
.doWrite(list);
}
}
测试结果:
EasyExcel读操作
第一步、引入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
第二步、创建实体类,必须和excel表中的数据相同,在对应的属性上面添加注解,设置表头内容
import com.alibaba.excel.annotation.ExcelProperty;
@Data
public class UserData1 {
@ExcelProperty(value = "用户编号",index = 0)
private int uid;
@ExcelProperty(value = "用户名称",index = 1)
private String username;
}
第三步、创建监听器
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.Map;
public class ExcelLister extends AnalysisEventListener<UserData1> {
//一行一行的去读取excel内容,从第二行读取,因为第一行是表头
@Override
public void invoke(UserData1 userData1, AnalysisContext analysisContext) {
System.out.println(userData1);
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取之后会执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
第四步、写测试类测试
import com.alibaba.excel.EasyExcel;
public class TestRead {
public static void main(String[] args) {
//读取文件路径
String fileName="D:\\excelData\\01.xlsx";
//调用方法实现读操作
EasyExcel.read(fileName,UserData1.class,new ExcelLister())
.sheet().doRead();
}
}
读取结果:
=========================================================================
四、缓存
缓存的作用:为了提高查询速度
那些数据适合做缓存:不经常修改的数据,固定的数据,经常查询的数据
Spring Cache 是一个非常优秀的缓存组件。自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,方便切换各种底层Cache(如:redis)
使用Spring Cache的好处:
1,提供基本的Cache抽象,方便切换各种底层Cache;
2,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
3,提供事务回滚时也自动回滚缓存;
4,支持比较复杂的缓存逻辑;
1、项目集成SpringCache + Redis
第一步:添加缓存相关依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
第二步:添加redis配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
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.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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
import java.time.Duration;
@Configuration
@EnableCaching //开启缓存的处理
public class RedisConfig {
/**
* 自定义key规则
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 设置RedisTemplate规则
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 设置CacheManager缓存规则
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer 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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
第三步、在需要使用缓存的模块的配置文件中添加redis设置
#配置redis
spring.redis.host=192.168.25.135
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
第四步、使用SpringCache
@Cacheable :根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
查看源码,属性值如下:
属性/方法 | 解释 |
value | 缓存名,必填,指定了你的缓存存在哪块命名空间 |
cacheNames | 与value差不多。二选一即可 |
key | 可选属性,可以使用SpEL标签自定义缓存的key |
//根据数据id查询子数据列表
@Override
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
public List<Dict> findChildData(Long id) {
QueryWrapper<Dict> dictQueryWrapper=new QueryWrapper<>();
dictQueryWrapper.eq("parent_id",id);
List<Dict> dicts = baseMapper.selectList(dictQueryWrapper);
//向list集合每个dict对象中设置hasChildren
for (Dict dict:dicts){
Long dictId = dict.getId();
boolean children = this.isChildren(dictId);
dict.setHasChildren(children);
}
return dicts;
}
@CacheEvict:使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
查看源码,属性值如下:
属性/方法名 | 解释 |
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
allEntries | 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 |
beforeInvocation | 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 |
//导入数据字典接口
@Override
@CacheEvict(value = "dict",allEntries = true)
public void importDictData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictLister(dictMapper))
.sheet()
.doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
测试:
此时Redis中没有值:
刷新数据字典后,Redis中有值了:
可以查看该值:
缓存设置成功