项目构建
<properties> |
OOOO
PO(persistant object):持久化对象
po 就是对应数据库中某个表的一条记录,多个记录可以用 po 的集合
DO(domain object):领域对象
就是从现实世界抽象出来的有形或无形的业务实体
TO(Transfer object):数据传输对象
不同的应用程序之间传输的对象
DTO(Data Transfer object):数据传输对象
这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的 数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这 里,泛指用于展示层与服务层之间的数据传输对象。
VO(Value object):值对象
通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由 GC 回收的。
View object:视图对象; 接受页面传递来的数据,封装对象将业务处理完成的对象,封装成页面要用的数据
BO(Business object):业务对象
从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对 象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作 用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简 历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经 历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每 个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。
Nacos注册中心
引入依赖
<!-- nacos-->
<!-- 根据本地安装的nacos1.4.1,使用 2.2.5.RELEASE 版本-->
<!-- 排除 netflix-ribbon ,加入 loadbalancer-->
<!-- spring cloud 从2021.0.5起,默认不在使用 bootstrap,需要导入-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>配置文件
# 将服务注册到Nacos
spring:
application:
# 服务名称
name: GuliMall-GateWay
cloud:
nacos:
# 服务地址:端口
server-addr: 127.0.0.1:8848开启服务注册发现
Nacos配置中心
引入依赖
<!-- nacos-->
<!-- 根据本地安装的nacos1.4.1,使用 2.2.5.RELEASE 版本-->
<!-- 排除 netflix-ribbon ,加入 loadbalancer-->
<!-- spring cloud 从2021.0.5起,默认不在使用 bootstrap,需要导入-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>添加配置文件
bootsrtap.yaml,优先级高于 application.properties
spring:
application:
# 服务名称
name: GuliMall-Coupon
cloud:
nacos:
# 配置中心地址
server-addr: 127.0.0.1:8848
config:
# 配置中心的文件后缀
file-extension: yaml
# 配置文件所属组
group: dev
# group: prop
# 配置命名空间(习惯于一个微服务开一个命名空间,用组名区分开发环境与上线环境等)
namespace: 6b7a84db-c36c-4197-ba58-b624c56947f2根据配置文件在Nacos添加配置文件 GuliMall-Coupon.yaml,group:dev
开启热部署
// 刷新配置
Feign
引入依赖
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 如果使用nacos作为注册中心的话,需要排除ribbon,并加入loadbalance依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>编写配置类
import com.heroxin.gulimall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
// 将服务注册到spring中,指定服务提供者的名称
public interface CouponFeignService {
// 服务提供者的路径
R membercoupons();
}调用服务
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("hero");
// couponFeignService 就是调用远程w
R membercoupons = couponFeignService.membercoupons();
return R.ok().put("member", memberEntity).put("coupons",membercoupons.get("coupons"));
}开启远程调用功能
// 加在服务消费者的启动类上
SpringBoot跨域处理
import org.springframework.context.annotation.Configuration; |
GateWay
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>配置
# 将服务注册到Nacos,需要nacos地址和服务名称
spring:
application:
# 服务名称
name: GuliMall-GateWay
cloud:
nacos:
# 注册中心地址
server-addr: 127.0.0.1:8848
gateway:
# 路由规则
routes:
# product
- id: product_rout
uri: lb://GuliMall-Product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>/?.*), /$\{segment}
# thirdparty
- id: thirdparty_rout
uri: lb://GuliMall-ThirdParty
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>/?.*), /$\{segment}
处理跨域请求
import org.springframework.context.annotation.Bean; |
Mybatis-Plus 逻辑删除
配置规则(高版本可以省略)
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 表示已删除
logic-not-delete-value: 0加上逻辑删除注解
private Integer showStatus;
JSR303
在 javax 包下
添加规则
/**
* 品牌名
* jsr303 数据校验
*/
private String name;开启校验
/**
* 保存
* @valild 开启校验功能
*/
public R save({ BrandEntity brand)
brandService.save(brand);
return R.ok();
}
VO
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
前后端进行数据传输时,只需要实体类中的某些字段或者需要表中没有的字段时,可以设置一个VO封装满足自己需求的实体类。
Windows10 端口占用
netstat –aon |findstr “80” |
TASKKILL /PID 20976 /F |
ElasticSearch
引入依赖
<dependency> |
Redis
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>配置文件
spring:
redis:
host: 192.168.196.101
port: 6379 # 默认配置
password: # redis没有设置密码就不要设置了代码
public Map<String, List<Catelog2Vo>> getCatalogJson() {
// 压入缓存,缓存中的数据是 json 字符串
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (StringUtils.isEmpty(catalogJson)) {
// 缓存中没有数据,将查到的数据放入缓存
Map<String, List<Catelog2Vo>> catalogJsonFromdb = getCatalogJsonFromdb();
// 序列化
// 将数据转化为json,json跨语言,跨平台兼容
String string = JSON.toJSONString(catalogJsonFromdb);
redisTemplate.opsForValue().set("catalogJson", string, 1, TimeUnit.DAYS);
}
// 反序列化,将json转化为我们指定的对象
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
Redis分布式锁
引入依赖
<!-- redis分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>RedissonClient
public class MyRedissonConfig {
/**
* 所有对redisson的使用都是通过调用redissonclient对象实现的
* @return
* @throws IOException
*/
public RedissonClient redisson() throws IOException{
// 创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.196.101:6379");
// 根据config配置创建从redissonclient实例
return Redisson.create(config);
}
}代码
public String testRLock() {
// 获取一把锁,只要锁的名字相同,就是同一把锁
RLock lock = redissonClient.getLock("my-lock");
// 加锁,锁的过期时间为 30 s
// 锁会自动续期 watchdog : 如果业务执行时间过长,运行期间会自动续期到 30s
// 不用担心业务运行过程中锁过期而被删除
// 加锁的业务只要运行完成,就不会进行锁续期。所以及时不手动解锁,在 30s 后锁就会被删除
lock.lock();
try {
// 业务代码
System.out.println("加锁成功,执行业务...");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
return null;
}读写锁
/**
* 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
* 写锁没释放,读锁必须等待
* 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
* 写 + 读 :必须等待写锁释放
* 写 + 写 :阻塞方式
* 读 + 写 :有读锁。写也需要等待
* 只要有读或者写的存都必须等待
*
*/
public String testWriteLock() {
String s = "";
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.writeLock();
try {
//改数据加写锁,读数据加读锁
rLock.lock();
s = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("writeValue",s);
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
public String testReadLock() {
String s = "";
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
//加读锁
RLock rLock = readWriteLock.readLock();
try {
rLock.lock();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
s = ops.get("writeValue");
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}信号量
/**
* 车库停车
* 3车位,3是自己在redis中设置的,key为信号量名,值为3
* 信号量也可以做分布式限流
*/
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
// park.acquire(); //获取一个信号、获取一个值,占一个车位
boolean flag = park.tryAcquire();
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
public String go() {
RSemaphore park = redisson.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}闭锁
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
public String gogogo( { Long id)
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}
SpringCache
Spring-Cache的不足之处:
1)、读模式
缓存穿透:查询一个null数据。解决方案:缓存空数据
缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间
2)、写模式:(缓存与数据库一致)
1)、读写加锁。
2)、引入Canal,感知到MySQL的更新去更新Redis
3)、读多写多,直接去数据库查询就行
总结:
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计
原理:
CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写
引入依赖
<!-- cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>配置文件
spring:
# 配置缓存
cache:
# 使用redis作为缓存
type: redis
redis:
# 设置过期时间为一小时,
time-to-live: 000配置类
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
public class MyCacheConfig {
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}开启缓存
public class GuliMallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GuliMallProductApplication.class, args);
}
}使用缓存
// 设置缓存分区、key
public List<CategoryEntity> getLevel1Category() {
List<CategoryEntity> entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return entities;
}缓存失效模式(更新后删除缓存)
// 缓存失效模式使用
//删除某个分区下的所有数据
public void updateCascade(CategoryEntity category) {
this.updateById(category);
System.out.println(category.getName());
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
SpringSession
引入依赖
<!-- spring-session-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>配置文件
server:
servlet:
session:
# session 过期时间
timeout: 30m
spring:
redis:
host: 192.168.196.101
port: 6379
# spring session
session:
# 存储类型
store-type: redis开启注解
public class GuliMallAuthApplication {
public static void main(String[] args) {
SpringApplication.run(GuliMallAuthApplication.class, args);
}
}对应的对象实现序列化
public class MemberResponseVo implements Serializable {}