Spring-day03

Spring-day03

01_Spring的AOP概述

目标

  • 了解AOP存在的意义及方式

路径

  1. AOP介绍
  2. AOP作用
  3. AOP优势
  4. Spring中AOP的实现方式

在前面我们提到:Spring技术的两大核心就是Ioc(控制反转)和AOP

AOP介绍

AOP(Aspect Oriented Programing) 面向切面编程,一种编程范式,指导开发者如何组织程序结构

  • OOP(Object Oriented Programing) 面向对象编程

AOP作用

在程序运行期间,不修改源码的基础上对已有方法进行增强(无侵入性: 解耦)

AOP优势

  1. 减少重复代码
  2. 提高开发效率
  3. 维护方便

Spring中AOP的实现方式

spring的AOP有两种实现方式:

  1. 基于JDK官方的动态代理(优先使用)

    • 当bean实现接口时,spring就会用JDK的动态代理
  2. 基于第三方的cglib的动态代理

    • 当bean没有实现接口时,spring使用CGLib来实现(JDK8后, JDK动态代理效率高于CGlib)
    备注: 开发者可以在spring中强制使用CGLib (了解)
    在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
    

02_Spring前置知识点

目标

  • 清楚动态代理的代码书写方式 (仅做了解)

路径

  1. 动态代理:Proxy
  2. 动态代理:CGLIb

需求:统计service层AccountServiceImpl类(有父接口)中所有方法的运行时间。

  • 要求:不修改源码的基础上, 对每个方法进行增强

动态代理:Proxy

JDK的Proxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强。

代码示例:

  • 业务层
@Service("accountService")
public class AccountServiceImpl implements IAccountService { //类有实现接口
    @Override
    public void saveAccount(Account account) {
        System.out.println("AccountServiceImpl => saveAccount方法");
    }

    @Override
    public void updateAccount(Account account) {
        System.out.println("AccountServiceImpl => updateAccount方法");
    }

    @Override
    public void deleteAccount(Integer id) {
        System.out.println("AccountServiceImpl => deleteAccount方法");
    }

    @Override
    public Account queryAccountById(Integer id) {
        System.out.println("AccountServiceImpl => queryAccountById方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Account> queryAllAccount() {
        System.out.println("AccountServiceImpl => queryAllAccount方法");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}
  • 测试类
public class JdkProxyTest {
    @Test
    public void testProxy() {
        //创建对象
        AccountServiceImpl accountService = new AccountServiceImpl();
        
        //类加载器
        ClassLoader classLoader = JdkProxyTest.class.getClassLoader();
        //父接口
        Class[] interfaces = AccountServiceImpl.class.getInterfaces();
        //处理器
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;//方法执行完后的返回值

                long beginTime = System.currentTimeMillis();//开时时间

                result = method.invoke(accountService, args);//执行方法

                long endTime = System.currentTimeMillis();//结束时间

                System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");

                return result;
            }
        };

        //使用jdk的Proxy获取代理对象
        IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance(classLoader, interfaces, handler);

        //使用代理对象调用方法
        List<Account> accountList = accountServiceProxy.queryAllAccount();
    }
}
//输出结果:
AccountServiceImpl => queryAllAccount方法
执行时间:3007毫秒

动态代理:CGLIB

CGLIB(Code Generation Library),Code生成类库 (第三方的库,不是JDK)

  • CGLIB动态代理不限定被代理类是否具有接口,可以对任意操作进行增强
  • 底层实现原理:继承
    • CGLIB动态代理继承于被代理类,动态创建出新的代理对象

代码示例:

使用cglib需要导入cglib的jar包
在Spring中已经整合了cglib,所以导入Spring-context包即可
  • 业务类
@Service("accountService2")
public class AccountServiceClass {  //类没有实现接口
    public Account queryAccountById(Integer id) {
        System.out.println("AccountServiceClass  =>  queryAccountById方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<Account> queryAllAccount() {
        System.out.println("AccountServiceClass  =>  queryAllAccount方法");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}
  • 测试类
import com.itheima.pojo.Account;
import com.itheima.service.AccountServiceClass;
import org.junit.Test;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.List;

public class CglibTest {
    @Test
    public void testCglib() {
        //1. 创建被代理类的对象
        AccountServiceClass accountService = new AccountServiceClass();

        //2. 创建代理类对象
        Callback callback = new MethodInterceptor() {
            /*
                intercept方法: 代理类对象执行任意方法,都会调用此方法
                1. proxy :表示代理类对象本身 (没什么用)
                2. method : 表示代理类对象当前调用的方法
                3. args : 表示代理类对象当前调用方法传入的参数
                4. methodProxy : 方法代理 (没什么用)
                返回值Object : 表示代理类对象当前调用方法产生的返回值
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                Object result = null;//方法执行完后的返回值
                long beginTime = System.currentTimeMillis();//开时时间

                result = method.invoke(accountService, args);//执行方法

                long endTime = System.currentTimeMillis();//结束时间
                System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");

                return result;
            }
        };
        Class type = accountService.getClass();
        AccountServiceClass proxy = (AccountServiceClass) Enhancer.create(type,callback);

        //3. 使用代理类对象调用方法
        List<Account> accountList = proxy.queryAllAccount();
        Account account = proxy.queryAccountById(1);
    }
}
//输出结果:
AccountServiceClass  =>  queryAllAccount方法
执行时间:3008毫秒
AccountServiceClass  =>  queryAccountById方法
执行时间:2010毫秒

03_Spring的AOP基础

目标

  • 了解Spring中AOP开发基础概念

路径

  1. AOP编程用到的概念
  2. AOP的开发过程
  3. AOP的开发方式

AOP编程用到的概念

面向切面编程中的相关概念:

image-20220429105931541

  1. Target(目标对象)
    • 要被增强的对象(被代理类的对象)
  2. Proxy(代理对象)
    • 对目标对象的增强对象(生成的代理类对象)
  3. Joinpoint(连接点)
    • 目标对象中的可被增强的方法(被代理类中的方法)
      • 不可被增强的方法:
        • Proxy中被代理类不可被增强方法(父接口中没有的方法)
        • CGlib中被代理类不可被增强方法(final修饰的方法)
  4. Pointcut(切入点)
    • 要被增强的方法(被代理类中要增强的方法)
      • 切入点一定是连接点;但连接点不一定是切入点
        • 切入点是要增强的方法,而该方法必须是连接点
        • 连接点,不一定要被增强
  5. Advice(通知)
    • 通知是增强的那段代码形成的方法
    • 通知的分类:
      1. 前置通知 在方法之前进行增强
      2. 后置通知 在方法之后进行增强
      3. 异常通知 在方法异常进行增强
      4. 最终通知 最终执行的方法进行增强
      5. 环绕通知 单独使用(以上所有通知)
  6. Aspect(切面)
    • 切面 = 切入点+通知
      • 目标方法和增强代码合到一起叫做切面
  7. Weaving(织入)
    • 在运行过程中spring底层将通知和切入点进行整合的过程称为织入

AOP的开发过程

开发阶段(开发者完成)

  • 正常的制作程序
  • 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
  • 将共性功能独立开发出来,制作成通知
  • 在配置类中,声明切入点
  • 在配置类中,声明切入点通知间的关系(含通知类型),即切面

运行阶段(AOP完成)

  • Spring容器加载配置文件时, 使用代理机制,动态创建目标对象代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,形成完整的代码逻辑
  • 切入点方法被运行,将会调用代理对象的方法,达到增强目标对象的效果

AOP的开发方式

AOP有两种开发方式:

  1. XML方式
  2. 注解方式(课程中学习这个)

04_Spring的AOP入门

目标

  • 能够使用Spring中的AOP配置增强类中方法

路径

  1. Spring中AOP开发步骤
  2. Spring的AOP注解
  3. 使用Spring的AOP方式统计业务类中方法执行时间

Spring中AOP开发步骤

在spring中使用aop开发的步骤:

  1. 导入相关坐标(Spring、切入点表达式)

  2. 开启aop注解支持

  3. 编写切面类

    • 配置切入点

    • 配置通知类型

  4. 运行程序(测试)

Spring的AOP注解

开启aop注解支持:

@EnableAspectJAutoProxy
public class SpringConfig{
}

切面类:

@Aspect  //配置当前类为切面类(切入点+通知)
public class ClassName{
    //配置切入点 : @Pointcut("切入点表达式")
    //定义切入点表达式:com.itheima包及子包下的AccountServiceImpl类中所有方法 
    /*
       举例1:针对AccountServiceImpl类中的queryAllAccount()方法进行增强
       切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAllAccount()
       
       举例2:针对AccountServiceImpl类中的queryAccountById(Integer id)方法进行增强
       切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAccountById(Integer)
       
        举例3:针对AccountServiceImpl类中的所有方法进行增强
        切入点表达式:com.itheima.service.impl.AccountServiceImpl.*(..)
       
    */
    @Pointcut("execution(* com.itheima..AccountServiceImpl.*(..))")
    public void pt(){
        //切入点,必须配置在三无方法上(无参、无返回值、空方法体)
    }
    
    //配置通知类型
    /* @Before: 前置通知
       @AfterReturning:后置通知
       @AfterThrowing :异常通知
       @After :最终通知
       @Around:环绕通知
    */
    @Before("pt()") //指定切点入(原因:明确要对哪个方法进行前置增强)
    public void 方法名(){
        //增强功能的代码
    }
    @AfterReturning("pt()")
    public void 方法名(){
        //增强功能的代码
    }
}

使用Spring的AOP方式统计业务类中方法执行时间

代码示例:

  • 导入坐标
<!-- spring核心jar包,已存在依赖的AOP的jar -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<!-- 切入点表达式 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
  • 配置类(开启aop注解支持)
@Configurable
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy  //开启aop注解支持
public class SpringConfig {
}
  • 业务层
//接口
public interface IAccountService {
    public void saveAccount(Account account);

    public void updateAccount(Account account);

    public void deleteAccount(Integer id);
    
    public Account queryAccountById(Integer id);

    public List<Account> queryAllAccount();
}


//实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Override
    public void saveAccount(Account account) {
        System.out.println("AccountServiceImpl => saveAccount方法");
    }

    @Override
    public void updateAccount(Account account) {
        System.out.println("AccountServiceImpl => updateAccount方法");
    }

    @Override
    public void deleteAccount(Integer id) {
        System.out.println("AccountServiceImpl => deleteAccount方法");
    }

    @Override
    public Account queryAccountById(Integer id) {
        System.out.println("AccountServiceImpl => queryAccountById方法");
        try {
            Thread.sleep(2000);//睡眠2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Account> queryAllAccount() {
        System.out.println("AccountServiceImpl => queryAllAccount方法");

        try {
            Thread.sleep(3000);//睡眠3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  • 切面类
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component //当前bean需要加载到ioc容器中
@Aspect //切面类(切入点+通知)
public class AccountServiceAdvice {
    //配置切入点     切入点表达式=com.itheima.service.impl.AccountServiceImpl类中所有方法
    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
    public void pt() {
        //切入点配置在无参无返回值的空方法上
    }

    long beginTime; //起始时间
    long endTime;//终止时间

    //配置通知
    @Before("pt()") //前置通知,在目标方法执行前运行
    public void beginTime() {
        System.out.println("开始计时...");
        beginTime = System.currentTimeMillis();
    }

    @AfterReturning("pt()") //后置通知,在目标方法执行后运行
    public void endTime() {
        endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(classes = SpringConfig.class) //加载注解配置类
public class AspectTest {
    @Autowired
    IAccountService accountService;

    @Test
    public void testAspect(){
        List<Account> accountList = accountService.queryAllAccount();

        Account account = accountService.queryAccountById(1);
    }
}
//输出结果:
开始计时...
AccountServiceImpl => queryAllAccount方法
执行时间:3002毫秒
开始计时...
AccountServiceImpl => queryAccountById方法
执行时间:2001毫秒

05_Spring的AOP详解-切入点

目标

  • 能够书写SpringAOP的切入点表达式

路径

  1. 案例分析
  2. 切入点表达式语法
  3. 切入点表达式示例

案例分析

spring的aop入门案例:

  1. 导入依赖
  2. 编写service层(接口、实现类[目标对象])
  3. 编写切面类(切入点 、通知)

aop入门案例执行机制:

  1. 创建目标对象:AccoucntServiceImpl (使用IoC配置)

  2. 创建代理对象

    2.1、基于目标对象 => 代理类

    2.2、基于AccountServiceAdvice => 代理类方法体

    • 切面 = 切入点 + 通知

    2.3、Weaving(织入)

    • 在运行过程中spring底层将通知和切入点进行整合
  3. 使用代理对象调用方法

    @Autowired
    IAccountService accountService; //代理对象
    
    //调用方法
    Account account = accountService.queryAccountById(1);
    

切入点表达式

通过切入点表达式可以让spring找到要增强的目标方法

要使用切入点表达式,需要添加依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

切入点表达式的语法:

  • 格式:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))

    示例:
    execution(public void com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
    
  • 通配符

    • 任意字符串:*
    • 任意重复次数:..
    示例:
    execution(public * com.itheima..AccountServiceImpl.*(..))
    
  • 规则

    1. 方法的修饰符可以省略
    2. 返回值可以使用*号代替(任意返回值类型)
    3. 包名可以使用*号代替,代表任意包(一层包使用一个*
    4. 使用..配置包名,标识此包以及此包下的所有子包
    5. 类名可以使用*号代替,标识任意类
    6. 方法名可以使用*号代替,表示任意方法
    7. 可以使用..配置参数,任意参数

切入点表达式示例

  • 省略方法的修饰符号

    execution(void com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
    
  • 使用*代替返回值类型

    execution(* com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
    
  • 使用*代替包名(一层包使用一个*

    execution(* com.itheima.*.*.AccountServiceImpl.deleteAccount(Integer))
    
  • 使用..省略包名

    execution(* com..AccountServiceImpl.deleteAccount(Integer))
    
  • 使用*代替类名

    execution(* com..*.deleteAccount(Integer))
    
  • 使用*代替方法名

    execution(* com..*.*(Integer))
    
  • 使用..省略参数

    execution(* com..*.*(..))
    

说明:在需求范围越具体越好(效率高)

//service包下的所有方法,均为切入点
execution(* com.itheima.service..*.*(..))

//service包下以Service结尾的业务类(如:IUserService、IAccountService、...)中所有方法
execution(* com.itheima.service.*Service.*(..))    

06_Spring的AOP详解-通知

目标

  • 能够配置Spring中AOP的通知

路径

  1. AOP中通知介绍
  2. AOP中环绕通知的使用

AOP中通知介绍

在AOP中有5种类型通知:

  1. 前置通知:@Before

    • 原始方法(切入点)执行前执行,如果通知中抛出异常,阻止原始方法运行
    • 应用场景:数据校验
    @Before("配置了切入点的方法名()")
    public void beforeMethod(){
        //前增强功能(在目标方法执行前运行)
    }
    
  2. 后置通知:@AfterReturning

    • 原始方法执行后执行,原始方法中出现异常,不再执行
    • 应用场景:返回值相关数据处理
    @AfterReturning("配置了切入点的方法名()")
    public void afterMethod(){
        //后增强功能(在目标方法执行后运行)
    }
    
  3. 抛出异常后通知:@AfterThrowing

    • 原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
    • 应用场景:对原始方法中出现的异常信息进行处理
    @AfterThrowing("配置了切入点的方法名()")
    public void throwMethod(){
        //在目标方法执行有异常时运行
    }
    
  4. 最终通知:@After

    • 无论如何最终都执行(不论是否有异常发生,都会执行)
    • 应用场景:清理资源
    @After("配置了切入点的方法名()")
    public void finallyMethod(){
        //不论是否有异常,在目标方法执行完后都会运行
    }
    
  5. 环绕通知:@Around

    • 在原始方法执行前后均有对应执行,还可以阻止原始方法的执行
    • 应用场景:可以做以上四种类型通知的所有事情(十分强大)

AOP中环绕通知的使用

环绕通知的开发方式:

  1. 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用

    @Around("配置了切入点的方法名()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();//调用原始方法
        return ret;
    }
    
  2. 环绕通知方法相关说明:

    • 方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方也可以设定返回值类型为void,最终返回null
    • 方法需在第一个参数位置设定ProceedingJoinPoint对象(代表切入点),通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行。
      • ProceedingJoinPoint相当于Method,但是比Method的封装度更高
    • 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息

代码示例:

  • 切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AccountServiceAdvice2 {
    //配置切入点
    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
    public void pt() {
        //切入点配置在无参无返回值的空方法上
    }

    @Around("pt()")         // ProceedingJoinPoint 相当于:Method
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object result =null ;//返回结果

        try {
            //前置增强
            System.out.println("开始计时...");
            long beginTime = System.currentTimeMillis();

            //执行原始方法(被增强的原方法)
            result = pjp.proceed();

            //模拟异常:
            //int num = 10/0;

            //后置增强
            long endTime = System.currentTimeMillis();
            Signature signature = pjp.getSignature();//获取切入点方法
            System.out.println(signature.getName()+" 执行时间:" + (endTime - beginTime) + "毫秒");

        } catch (Throwable throwable) {
            System.out.println("异常通知");
        } finally {
            System.out.println("最终通知");
        }

        return result;//返回执行结果
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(classes = SpringConfig.class) //加载注解配置类
public class AspectTest {
    @Autowired
    IAccountService accountService;

    @Test
    public void testAspect(){
        List<Account> accountList = accountService.queryAllAccount();

        Account account = accountService.queryAccountById(1);

        accountService.deleteAccount(1);

        accountService.saveAccount(null);
    }
}
//输出结果:
开始计时...
AccountServiceImpl => queryAllAccount方法
queryAllAccount 执行时间:3001毫秒
最终通知
开始计时...
AccountServiceImpl => queryAccountById方法
queryAccountById 执行时间:2002毫秒
最终通知
开始计时...
AccountServiceImpl => deleteAccount方法
deleteAccount 执行时间:0毫秒
最终通知
开始计时...
AccountServiceImpl => saveAccount方法
saveAccount 执行时间:0毫秒
最终通知

07_事务案例-转账

目标

  • 为学习后续的Spring事务管理做铺垫

路径

  1. 事务介绍
  2. 案例:转账业务

事务介绍

事务:transaction

  • 一组SQL操作,要么全部成功,要么全部失败

事务操作步骤:

  1. 开启事务
  2. 业务操作(多条sql语句)
  3. 提交事务/回滚事务

事务的特性:(ACID)

  • 原子性(Atomicity)
    • 指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
  • 一致性(Consistency)
    • 事务前后数据的完整性必须保持一致
  • 隔离性(Isolation)
    • 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
  • 持久性(Durability)
    • 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

事务的隔离级别:

隔离级别 说明 脏读 不可重复读 幻读
READ_UNCOMMITTED 读未提交
READ_COMMITTED 读已提交 ×
REPEATABLE_READ 可重复读 × ×
SERIALIZABLE 串行化操作 × × ×
  • 隔离级别由低到高:
    • READ_UNCOMMITTED => READ_COMMITTED => REPEATABLE_READ => SERIALIZABLE
  • 数据库默认隔离级别:
    • MySQL采用:REPEATABLE_READ(可重复读)
    • Oracle采用:READ__COMMITTED(读已提交)

案例:转账业务

需求:jack给rose转账500元

数据准备:

create database spring_db;
use spring_db;
create table account(
	id int primary key auto_increment,
	name varchar(20),
	money double	
);
insert into account values(null,'jack',1000),(null,'rose',1000);

导入素材中的代码:day03-transfer

代码示例:

  • 数据库配置文件:jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=itheima
  • Pojo类
public class Account {
    private Integer id;
    private String name;
    private Double money;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
}
  • Mybatis配置类
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource ds){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //设置pojo的包扫描
        factoryBean.setTypeAliasesPackage("com.itheima.pojo");
        //设置连接池
        factoryBean.setDataSource(ds);

        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        //设置dao层的接口扫描
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
  • Spring配置类
@Configuration
@ComponentScan("com.itheima")//把bean加载到IOC容器
@PropertySource("classpath:jdbc.properties")
@Import(MybatisConfig.class)
@EnableAspectJAutoProxy   //开启aop注解支持
public class SpringConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
  • Dao层
public interface AccountDao {
    //转出
    @Update("update account set money = money - #{money} where id = #{outId}")
    public abstract int outMoney(@Param("outId") int outId, 
                                 @Param("money")double money);
    
    //转入
    @Update("update account set money = money + #{money} where id = #{inId}")
    public abstract int inMoney(@Param("inId") int inId, 
                                @Param("money")double money);
}
  • 业务层
//接口
public interface AccountService {
    //转账业务
    public abstract void transfer(int outId,int inId,double money);
}


//实现类
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao dao;

    @Override
    public void transfer(int outId, int inId, double money) {
        try {
            //转出
            dao.outMoney(outId, money);
            
            //可能在转账过程中发生意外: 转出执行,转入还未执行
            //int i = 1/0;
            
            //转入
            dao.inMoney(inId, money);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class WebApp {
    @Autowired
    private AccountService service;

    @Test
    public void test01(){
        service.transfer(1, 2, 500);
    }
}

08_Spring事务管理对象

目标

  • 能够写出Spring中事务管理对象API的基础代码

路径

  1. Spring事务核心对象介绍
  2. 事务管理器对象
  3. 事务定义对象
  4. 事务状态对象

Spring事务核心对象介绍

  • JavaEE开发使用分层设计的思想进行

    一般dao层只做数据库增删改查实现,当业务中包含多个dao层的调用时,需要在service层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理

  • Spring为业务层提供了整套的事务解决方案

    • PlatformTransactionManager //平台事务管理器
    • TransactionDefinition //事务定义
    • TransactionStatus //事务状态

事务管理器对象

PlatformTransactionManager接口(平台事务管理器)

  • DataSourceTransactionManager类
    • 适用于SpringJDBC或MyBatis
  • HibernateTransactionManager类
    • 适用于Hibernate3.0及以上版本
  • JpaTransactionManager类
    • 适用于JPA (Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行)

PlatformTransactionManager接口定义了事务的基本操作:

  • 获取事务

    TransactionStatus getTransaction(TransactionDefinition definition)
    
  • 提交事务

    void commit(TransactionStatus status)
    
  • 回滚事务

    void rollback(TransactionStatus status)
    

代码示例:

//1. 创建事务管理器
 DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//2. 为事务管理器设置与数据层相同的数据源
 dstm.setDataSource(dataSource);

事务定义对象

TransactionDefinition接口(事务定义)

  • DefaultTransactionDefinition类

    DefaultTransactionDefinition td = new DefaultTransactionDefinition();
    

TransactionDefinition接口定义了事务的基本信息:

  • 事务隔离级别

    • spring默认使用和数据库一样的隔离级别
    //设置事务的隔离级别
    td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
    //mysql默认隔离级别是REPEATABLE_READ
    //oracle默认隔离级别是READ_COMMITTED
    
  • 事务是否只读

    //设置是否为只读事务
    td.setReadOnly(false);
    //false, 表示可读可写(适合增删改操作)【默认设置】
    //true, 表示只读(适合查,效率高)
    
  • 事务超时时间(单位:秒)

    //设置超时时间
    td.setTimeout(10);//超时时间为10秒
    //默认值是-1, 表示永不超时
    
  • 事务传播行为

    //设置事务传播行为 (这个比较复杂,后续详解)
    td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    

示例代码:

//创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为 
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

事务状态对象

TransactionStatus接口(事务状态)

  • 定义了事务在执行过程中某个时间点上事务对象的状态信息
获取事务是否处于新开启事务状态
boolean isNewTransaction();

获取事务是否处于完成状态
boolean isCompleted();

//获取事务是否处于回滚状态
boolean isRollbackOnly();

//获取事务是否具备回滚存储点
boolean hasSavepoint();

//设置事务处于回滚状态
void setRollbackOnly()

//刷新事务状态
void flush();

完整代码示例:

/* Spring提供的事务管理对象:
     平台事务管理器对象 :PlatformTransactionManager接口
     事务定义对象:TransactionDefinition接口
     事务状态对象:TransactionStatus接口
*/
//1、创建事务管理器对象(Mybatis专用)
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置数据源
dstm.setDataSource(dataSource);//数据库连接池对象和事务管理器绑定了

//2、创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为 
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

//3、创建事务状态对象   (用于控制事务执行[相当于开启事务]) 
TransactionStatus ts = dstm.getTransaction(td);

//省略:业务层调用dao层功能代码

// 提交事务
dstm.commit(ts); 
// 回滚事务
dstm.rollback(ts);

简化:

//1、创建事务管理器对象
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//2、把事务器对象和数据库连接池进行绑定
dstm.setDataSource(dataSource);

//3、开启事务
TransactionStatus ts = dstm.getTransaction(new DefaultTransactionDefinition());

//4、事务操作(业务)
业务层中的代码.....


//5、提交事务|回滚事务
dstm.commit(ts); 

09_Spring事务-编程式事务

目标

  • 了解Spring中编程式事务的使用

路径

  1. 使用编程式事务改造转账业务

spring事务管理有两种方式:

  1. 编程式(了解)
  2. 声明式(重点)

使用编程式事务改造转账业务

使用spring提供的api进行事务管理,对转账业务代码进行修改:

/*
    TODO : Spring编程式事务
    1. 解释: 用Spring中的事务相关API用编码的方式来实现事务
    2. 但是这样实现不好: 耦合严重
        1). 事务管理代码跟业务代码耦合在一起
        2). 如果后续还有其他业务方法需要事务, 还得一个个编写事务代码,很冗余
    3. 解决方案:
        AOP
        1). 特点: 在不惊动原始设计的前提下,增强方法
        2). 概述: 无侵入性, 解耦
 */
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private DataSource dataSource;

    /**
     * 功能: 转账
     *
     * @param outId 出款人id
     * @param inId  收款人id
     * @param money 转账金额
     */
    @Override
    public void transfer(int outId, int inId, double money) {
        //1. 创建事务管理器
        DataSourceTransactionManager dstm = new DataSourceTransactionManager();
        //为事务管理器设置与数据层相同的数据源!!!
        dstm.setDataSource(dataSource);
        //2. 创建事务定义对象 : 隔离级别/传播特性/超时时间...
        DefaultTransactionDefinition td = new DefaultTransactionDefinition();
        /*
           设置事务隔离级别
           0). spring默认隔离级别是跟数据库软件一致 (ISOLATION_DEFAULT)
           1). mysql默认是REPEATABLE_READ
           2). oracle默认是READ_COMMITTED
        */
        td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        /**
         * 设置是否为只读事务
         * 1). false,表示读写均可(默认设置,适合增删改操作)
         * 2). true,表示只读(适合查,效率高)
         */
        td.setReadOnly(false);
        /**
         * 设置超时时间
         * 1). 默认值是-1, 表示永不超时
         * 2). 单位是秒
         */
        td.setTimeout(10);
        /**
         * 设置事务传播行为 (这个比较复杂,后续详解)
         * 1. 一般增删改:REQUIRED (默认值)
         * 2. 一般查询  SUPPORTS
         */
        td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //3.创建事务状态对象,用于控制事务执行(了解)  -> 相当于开启事务
        TransactionStatus ts = dstm.getTransaction(td);

        try {
            //转出
            accountDao.outMoney(outId, money);
            //可能在转账过程中发生意外: 转出执行,转入还未执行
            int i = 1 / 0;
            //转入
            accountDao.inMoney(inId, money);
            //成功:事务提交
            dstm.commit(ts);
        } catch (Exception e) {
            //e.printStackTrace();
            //失败:事务回滚
            dstm.rollback(ts);
        }
    }
}

以上代码中存在的问题:

  1. 事务管理代码跟业务代码耦合在一起(耦合严重)
  2. 如果后续还有其他业务方法需要事务, 还得一个个编写事务代码(过于冗余)

解决方案:AOP

  • 无侵入性, 解耦(在不惊动原始代码设计的前提下添加事务管理)

10_Spring事务-AOP改造编程式事务

目标

  • 了解使用Spring的AOP改造编程式事务的方式

路径

  1. 使用Spring的AOP改造编程式事务

使用Spring的AOP改造编程式事务

使用spring的aop改造案例中的事务管理代码:

  • Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)

//开启AOP支持
@EnableAspectJAutoProxy  
public class SpringConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
  • 修改业务类
    • 注意:不要在转账业务代码中添加异常处理代码
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    /**
     * 功能: 转账
     *
     * @param outId 出款人id
     * @param inId  收款人id
     * @param money 转账金额
     */
    @Override
    public void transfer(int outId, int inId, double money) {
        /**
         * 注意: 在aop使用中,切入点方法千万不能自己catch异常
         *      原因: 如果切入点自己catch了异常,那么通知中是调用切入点的地方是不会感知到异常,就不会执行catch了
         *           (相当于异常通知失效)
         *      解决方案:
         *          方案1: 有异常直接抛出,不要catch
         *          方案2: 可以catch,但是再new一个异常抛出
         */
        //转出
        accountDao.outMoney(outId, money);

        //可能在转账过程中发生意外: 转出执行,转入还未执行
        int i = 1 / 0;

        //转入
        accountDao.inMoney(inId, money);
    }
}
  • 切面类:事务管理
@Component
@Aspect
public class TransactionAdvice { //事务通知类
    @Autowired
    private DataSource dataSource;

    //切入点
    @Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.transfer(..))")
    public void pt(){
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object result = null;
        //1. 创建事务管理器
        DataSourceTransactionManager dstm = new DataSourceTransactionManager();
        //为事务管理器设置与数据层相同的数据源!!!
        dstm.setDataSource(dataSource);
        //2. 创建事务定义对象 : 隔离级别/传播特性/超时时间...
        DefaultTransactionDefinition td = new DefaultTransactionDefinition();
        /*
           设置事务隔离级别
           0). spring默认隔离级别是跟数据库软件一致 (ISOLATION_DEFAULT)
           1). mysql默认是REPEATABLE_READ
           2). oracle默认是READ_COMMITTED
        */
        td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        /**
         * 设置是否为只读事务
         * 1). false,表示读写均可(默认设置,适合增删改操作)
         * 2). true,表示只读(适合查,效率高)
         * /
         td.setReadOnly(false);
         /**
         * 设置超时时间
         * 1). 默认值是-1, 表示永不超时
         * 2). 单位是秒
         */
        td.setTimeout(10);
        /**
         * 设置事务传播行为 (这个比较复杂,后续详解)
         * 1. 一般增删改:REQUIRED (默认值)
         * 2. 一般查询  SUPPORTS
         */
        td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //3.创建事务状态对象,用于控制事务执行 -> 相当于开启事务
        TransactionStatus ts = dstm.getTransaction(td);

        try {
            //执行原始方式
            result = pjp.proceed();
            //成功:事务提交
            dstm.commit(ts);
        } catch (Throwable throwable) {
            //throwable.printStackTrace();
            System.out.println("转账业务异常!");
            //失败:事务回滚
            dstm.rollback(ts);
        }
        //返回值
        return result;
    }
}

11_Spring事务-声明式事务

目标

  • 使用声明式事务解决转账业务

路径

  1. 声明式事务前言
  2. 声明式事务开发步骤
  3. 使用声明式事务实现转账业务

声明式事务前言

AOP配置事务不具备特例性(通俗的说:任何业务添加事务都是一样的操作)

Spring底层封装了事务通知类,直接配置即可用。称为:声明式事务

  • 也就说, 之前案例中的TransactionAdvice类+相应的AOP配置可以不写了

声明式事务的开发步骤

声明式事务的开发步骤:

  • 第1步:开启事务管理支持

    @EnableTransactionManagement
    
  • 第2步:配置事务管理器

    @Bean
    public DataSourceTransactionManager getTxManager(DataSource dataSource){
        //创建事务管理器
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);//给事务管理器设置数据源
        return manager;
    }
    
  • 第3步:配置需要事务支持的切入点

    @Transactional(
        isolation = Isolation.DEFAULT,  //使用数据库默认的隔离级别
        readOnly = false,  //不是只读事务(增删改查都可以)
        timeout = 10,  //超时时间 
        propagation = Propagation.REQUIRED //设置事务传播行为
    )
    void transfer(int outId,int inId,double money);
    

使用声明式事务实现转账业务

代码示例:

  • Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)
//@EnableAspectJAutoProxy  //开启AOP支持
//声明式事务第1步:开启事务管理支持
@EnableTransactionManagement
public class SpringConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    //声明式事务第2步:配置事务管理器
    @Bean
    public DataSourceTransactionManager getTxManager(DataSource dataSource){
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);
        return manager;
    }
}
  • 业务层
//接口
public interface AccountService {
    //声明式事务第3步:配置需要事务支持的切入点
    @Transactional(
            isolation = Isolation.DEFAULT,
            readOnly = false,
            timeout = 10,
            propagation = Propagation.REQUIRED
    )
    //转账业务
    public abstract void transfer(int outId, int inId, double money);
}
  • @Transactional注解书写位置
    1. 可以直接放在接口上,表示该接口的所有方法都是切入点 (推荐)
    2. 可以放在接口方法上,表示该方法的所有重写方法都是切入点 (推荐)
    3. 放在实现类上,表示该实现类所有方法都是切入点
    4. 放在实现类的方法上,表示该方法是切入点

12_Spring事务传播行为

目标

  • 能够讲出Spring中常用的事务传播行为

路径

  1. 什么是事务传播行为
  2. 事务传播行为的设置
  3. 事务传播行为的应用场景
  4. 事务传播行为示例

什么是事务传播行为

事务传播行为:

  • 在一个业务流程(有事务的业务流)中,通常会调用多个方法执行,而每个方法对于事务的处理态度

image-20220513115431498

事务传播行为的设置

@Transactional(
    propagation = Propagation.REQUIRED //事务传播行为
)
public abstract void transfer(int outId, int inId, money money);

事务传播行为Propagation的取值:

  • REQUIRED (默认的传播行为)
    • 支持当前事务,如果不存在,就新建一个
  • SUPPORTS
    • 支持当前事务,如果不存在,就不使用事务
  • REQUIRES_NEW
    • 不论当前事务是否存在,都创建一个新的事务
  • NOT_SUPPORTED
    • 以非事务方式运行,如果有事务存在,挂起当前事务
  • MANDATORY
    • 支持当前事务,如果不存在,抛出异常
  • NEVER
    • 以非事务方式运行,如果有事务存在,抛出异常
  • NESTED
    • 如果当前事务存在,则嵌套事务执行(一个事务, 在A事务调用B过程中,B失败了,回滚事务到之前SavePoint ,用户可以选择提交事务或者回滚事务)

常用事务传播行为:

一般增删改:加事务 REQUIRED  (默认取值)
一般查询:不加事务 SUPPORTS
非主要业务不对主业务造成影响:REQUIRES_NEW

事务传播行为的应用场景

应用场景举例:转账操作

转账业务:transfer()
    - 转出转入 inAndOut() :X用户执行转出操作,修改表 , Y用户执行转入操作,修改表 
    - 记录日志 saveLog() :银行记录转账日志到数据库日志表中 
业务逻辑:
	1). 如果"转账"操作失败了, "转出转入"需要回滚
		如果"转账"有事务,"转出转入"跟随即可, 如果"转账"没有事务,"转出转入"需要自己创建事务
		所以"转出转入"适合设置传播行为属性为:REQUIRED
		
	2). 如果"转账"操作失败了, "记录日志"不需要回滚,必须要执行
    	如果"转账"有事务, "记录日志"不跟随(创建新事务)
    	如果"转账"没有事务, "记录日志"创建事务
    	所以"记录日志"适合设置传播行为属性为:REQUIRES_NEW

事务传播行为示例

事务传播行为是发生在两个Bean之间的

代码示例:

  • 数据表:
#日志表
create table db_log(
    id int primary key auto_increment,
    message varchar(20)
);
insert into db_log values(null,"测试");
  • Mybatis配置类
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource ds){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        //设置pojo的包扫描
        factoryBean.setTypeAliasesPackage("com.itheima.pojo");
        //设置连接池
        factoryBean.setDataSource(ds);

        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        //设置dao层的接口扫描
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
  • Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)
//声明式事务第1步:开启事务管理支持
@EnableTransactionManagement
public class SpringConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    //声明式事务第2步:配置事务管理器
    @Bean
    public DataSourceTransactionManager getTxManager(DataSource dataSource){
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);
        return manager;
    }
}
  • 业务层
//接口
public interface AccountService {
    //转账业务
    public abstract void transfer(int outId, int inId, double money);
}

//实现类
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountServiceClass accountService;

    /**
     * 功能: 转账
     *
     * @param outId 出款人id
     * @param inId  收款人id
     * @param money 转账金额
     */
    @Override
    //声明式事务第3步:配置需要事务支持的切入点
    @Transactional
    public void transfer(int outId, int inId, double money) {
        try{
            //转出转入
            accountService.inAndOut(outId,inId,money);
        }finally {
            //记录日志
            accountService.saveLog();
        }
    }
}



//业务类
@Service
public class AccountServiceClass {
    @Autowired
    private AccountDao accountDao;

    //转出转入
    @Transactional(
            propagation = Propagation.REQUIRED //默认值
    )
    public void inAndOut(int outId, int inId, float money){
        //转出
        accountDao.outMoney(outId, money);
        //可能在转账过程中发生意外: 转出执行,转入还未执行
        int i = 1 / 0;
        //转入
        accountDao.inMoney(inId, money);
    }

    //日志记录
    @Transactional(
            propagation = Propagation.REQUIRES_NEW //开启新事务
    )
    public void saveLog() {
        accountDao.insertLog("转账日志");
    }
}
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class WebApp {
    @Autowired
    private AccountService accountService;

    @Test
    public void test01(){
        accountService.transfer(1, 2, 500);
    }
}
posted @   忘了鱼尾纱的猫  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
  1. 1 刘哈哈与大先生 刘心&大鹏
  2. 2 我们打着光脚在风车下跑,手上的狗尾巴草摇啊摇 等一下就回家 / -艾兜
  3. 3 哎呦 毛不易
  4. 4 夜、萤火虫和你 AniFace
我们打着光脚在风车下跑,手上的狗尾巴草摇啊摇 - 等一下就回家 / -艾兜
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词:等一下就回家/艾兜

作曲:等一下就回家/艾兜

混音:裴济逸

编曲:HYPER MUSIC

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

让我忘记时间to the midnight

the sun came out 把所有染成金色的

风风风让它吹过来

至少年轻我还记得

oh 找一个地方落下

躺在谷仓里

和你讲着小话

什么风都吹不倒它

它就像是活的

知道哪是它的家

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

我们打着光脚在那风车下跑

等一下就回家怎么才到半山腰

就让那些烦恼都随风去吧

随着稻香飘过的地方耶哎呦喂

喜欢那时候风言风语

总是习惯悲中带着笑

喜欢被无视的童言无忌

被风车带走不在

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

点击右上角即可分享
微信分享提示