承接上一篇路线
首先我们的的事务控制是基于AOP的那么什么是AOP呢,之前我们在spring的学习中已经写到过了
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
原文链接:https://blog.csdn.net/weixin_53227758/article/details/122379264
execution(表达式) 简单入门
实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.itlanlan.service.impl.*.*(..)
在pom文件中导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
编写AOP类
@Aspect
@Component
public class LogAspect {
//com/example/demo08aoptransactional/Controller
@Pointcut("execution(public * com.example.demo08aoptransactional.Controller.*.*(..))")
public void webLog(){}
/**
* 前置通知
*/
// @Before("webLog()")
public void beforeprintLog(){
System.out.println("Logger方法开始记录日志了...");
}
/**
* 后置通知
*/
// @AfterReturning("webLog()")
public void afterReturningPrintLog(){
System.out.println("Logger方法结束记录日志了...");
}
/**
* 异常通知
*/
// @AfterThrowing("webLog()")
public void afterThrowingPrintLog(){
System.out.println("Logger方法错误记录日志了...");
}
/**
* 最终通知
*/
// @After("webLog()")
public void afterPrintLog(){
System.out.println("Logger最终通知");
}
//环绕通知,环绕增强,相当于MethodInterceptor
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
System.out.println("方法环绕start.....");
try {
Object o = pjp.proceed();
System.out.println("方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}finally {
System.out.println("方法环绕结束,提交事务");
}
}
}
这样我们springboot的通知管理就ok了
@Transactional 是声明式事务管理 编程中使用的注解
1)接口实现类或接口实现方法上,而不是接口类中。
2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。
3)错误使用:
1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
2.接口中异常(运行时异常)被捕获而没有被抛出。
默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
也不能获取spring 注入的 bean 。
在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
不会回滚线程中调用方法的事务。
首先来看我们的sql语句
javabean类
@Data
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
}
Mapper类
@Repository
@Mapper//标注会对其进行扫描
public interface FirstDao {
@Select("select * from account where id = #{id}")
Account findById(Integer id);
@Update("update account set money = #{money} where id = #{id}")
void updatemoney(Account account);
}
service类
@Service
@Transactional//开启事务控制
public class FirstServiceimpl implements FirstService{
@Autowired
FirstDao firstDao;
@Override
public void transfer(Integer A, Integer B) {
Account a = firstDao.findById(A);
Account b = firstDao.findById(B);
a.setMoney(a.getMoney()-100);
b.setMoney(b.getMoney()+100);
System.out.println(a);
System.out.println(b);
firstDao.updatemoney(b);
int e = 10/0;
firstDao.updatemoney(a);
}
}
测试方法
@Test
void testService(){
firstService.transfer(2,3);
}
@Transactional//开启事务控制
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读,
不可重复读) 基本不使用@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化
我们通过这一个注解就实现了在service层的对事务的控制
Spring从3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;
• Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
• Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
• 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下次调用直接从缓存中获取。
• 使用Spring缓存抽象时我们需要关注以下两点; 1、确定方法需要被缓存以及他们的缓存策略 2、从缓存中读取之前缓存存储的数据
简单来说,缓存就是把数据存到内存中
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
@EnableCaching
@SpringBootApplication
public class Demo09CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Demo09CacheApplication.class, args);
}
}
/**
* 默认使用ConcurrentMapCache将数据保存到一个hasMap里面
*/
@CacheConfig(cacheNames = "emp")
//这指定了name=emp下面的就不用写了
@Slf4j
@Service
@Transactional//开启事务控制
public class FirstServiceimpl implements FirstService {
@Autowired
FirstDao firstDao;
/**
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0]
* getEmp[2]
* key = "#root.methodName+'['+#id+']'"
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用;
*
*
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
*
* condition:指定符合条件的情况下才缓存;
* condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
* @param id
* @return
*/
/*
@Cacheable保存缓存
*/
@Cacheable(cacheNames = "account")
@Override
public Account getAccount(Integer id) {
log.info("service..执行了");
return firstDao.getAccountById(id);
}
/*
@CachePut即调用方法也更新缓存
因为两个的key不一样所以无法使用
key = "#result.id"
*/
@Override
@CachePut(cacheNames = "account",key = "#result.id")
public Account updateAccount(Account account) {
firstDao.updateAccount(account);
log.info("service..执行了");
return account;
}
/*
@CacheEvict清除缓存
allEntries = true 清除所有缓存
beforeInvocation = true 缓存的清除是否在方法之前执行
默认方法执行之后清除缓存
*/
@Override
@CacheEvict(cacheNames = "account", key = "#id")
public void deleteAccount(Integer id){
firstDao.deleteAccount(id);
}
/*
@Caching编辑复杂的查询
*/
@Override
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#name")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.name")
}
)
public Account getAccountByName(String name){
return firstDao.getAccountByName(name);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
#redis的基础配置
redis:
host: 8.142.109.111
port: 6379
password: 123456
对于缓存时间的全局设置:
application.properties:
# 对基于注解的Redis缓存数据统一设置有效期为1分钟,单位毫秒
spring.cache.redis.time-to-live=60000
和之前的一样
* 原理:CacheManager===Cache 缓存组件来实际给缓存中存取数据
* 1)、引入redis的starter,容器中保存的是 RedisCacheManager;
* 2)、RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
* 3)、默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json
* 1、引入了redis的starter,cacheManager变为 RedisCacheManager;
* 2、默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate
@Autowired
RedisTemplate redisTemplate;
@Test
void testRedis(){
redisTemplate.opsForValue().append("hello","world");
String hello = (String) redisTemplate.opsForValue().get("hello");
System.out.println(hello);
}
@Test
//保存对象
void test02(){
redisTemplate.opsForValue().set("emp01",firstDao.getAccountById(1));
Account emp01 = (Account) redisTemplate.opsForValue().get("emp01");
System.out.println(emp01);
}
这是通过RedisTemplate来实现缓存
(){
redisTemplate.opsForValue().append(“hello”,“world”);
String hello = (String) redisTemplate.opsForValue().get(“hello”);
System.out.println(hello);
}
@Test
//保存对象
void test02(){
redisTemplate.opsForValue().set(“emp01”,firstDao.getAccountById(1));
Account emp01 = (Account) redisTemplate.opsForValue().get(“emp01”);
System.out.println(emp01);
}
这是通过RedisTemplate来实现缓存