Spring授课笔记3

1 AOP注解方式:在通知中获取连接点细节信息

| //这是一个切面@Aspect//要创建Bean并放入到IoC容器中@Componentpublic class LogAspect {
@Before(value = “MyPointcut.pointcut1()”)
public void beforeLog(JoinPoint joinPoint){ //add(int int) sub(int int)
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();//方法名 add
int modifiers = signature.getModifiers(); //修饰符 public
String declaringTypeName = signature.getDeclaringTypeName();//方法属于哪个类
//joinPoint.getTarget(); //目标对象
//joinPoint.getThis();//代理对象
// System.out.println(“[日志]:”+modifiers+” “+ Modifier.toString(modifiers)+” “+declaringTypeName);

  1. System.**_out_**.println(**"[日志]:"**+methodName+**"方法开始执行,参数是:"**+ Arrays._toString_(args));<br /> }<br /> @AfterReturning(value = **"MyPointcut.pointcut1()"**,returning = **"result1"**)<br /> **public void **afterSuccessLog(JoinPoint joinPoint,Object result1){<br /> String methodName = joinPoint.getSignature().getName();<br /> System.**_out_**.println(**"[日志]:"**+methodName+**"方法执行成功结束,结果是:"**+result1);<br /> }<br /> @AfterThrowing(value = **"MyPointcut.pointcut1()"**,throwing=**"ex"**)<br /> **public void **afterExceptionLog(Exception ex){<br /> System.**_out_**.println(**"[日志]:方法执行异常结束,异常是:"**+ex);<br /> }<br /> @After(**"MyPointcut.pointcut1()"**)<br /> **public void **afterFinallyLog(){<br /> System.**_out_**.println(**"[日志]:方法执行结束Finally,执行最后的扫尾操作:"**);<br /> }<br />} |

| —- |

2 AOP注解方式:切入点的详细语法

  • 切入点是某包某类的无参数的方法:

示例:execution(* com.atguigu.service.impl.Student.test())

  • 切入点是某包某类带有参数的方法

示例: execution(* com.atguigu.service.impl.Student.test(String,int))

  • 切入点是某包某类的某个同名的所有方法

示例:.. 表示任意个数任意类型的参数
execution(* com.atguigu.service.impl.Student.test(..))

  • 切入点是某包下的某类的所有方法

示例:表示任意的类名,方法名,包名
execution(
com.atguigu.service.impl.Student.*(..))

  • 切入点是某包下的所有类的所有方法

示例: 表示任意的类名,方法名,包名
execution(
com.atguigu.service.impl. . (..))

注意:

    • 如何通配public int
  1. 如何通配任意的一级包(.)和多级包(..)
  2. 如何通配没有参数的方法()和任意参数的方法(..)

    3 AOP注解方式:环绕通知

    | //这是一个切面@Aspect//创建Bean对象放入IoC容器@Componentpublic class TransactionAspect {
    @Around(“execution( com.atguigu.service.CalculatorPureImpl.(..))”)
    public Object manageTransaction(ProceedingJoinPoint pjp){
    Object result = null;
    try{
    //调用目标对象之前通知:前置通知
    System.out.println(“[事务] 开启事务 conn.setAutoCommit(false) “);
    //调用目标对象的方法
    result = pjp.proceed(); // 调用下一个切面或者目标方法
    //特别像过滤器中 chain.doFilter(request,response)
    //调用目标对象成功结束之后通知:返回通知
    System.out.println(“[事务] 提交事务 conn.commit()”);
    }catch(Throwable e){
    //调用目标对象异常结束后通知:异常通知
    System.out.println(“[事务] 回滚事务 conn.rollback()”);
    }finally{
    //不管成功失败都执行的通知:后置通知
    System.out.println(“[事务] 不管提交回滚都需要执行该操作”);
    }

     //返回目标方法的执行结果<br />        **return **result;<br />    }<br />} |
    

    | —- |

使用@Around通知,需要使用ProceedingJoinPoint而不是JoinPoint。

Spring授课笔记3 - 图1

4 AOP注解方式:优先级Order

如何确定各个切面的执行顺序,通过@Order指定,值越小越早执行

//这是一个切面@Aspect//创建Bean对象放入IoC容器@Component
@Order(4)public class TransactionAspect {
}
//这是一个切面@Aspect//要创建Bean并放入到IoC容器中@Component
@Order(3)public class LogAspect {

Spring授课笔记3 - 图2

Spring授课笔记3 - 图3

Spring授课笔记3 - 图4

5 AOP注解方式:没有接口的情况

情况1:没有切面

Spring授课笔记3 - 图5

情况2:使用切面,但是有接口(底层使用JDK的动态代理)
public class CalculatorPureImpl implements Calculator
Spring授课笔记3 - 图6

情况3:使用切面,但是没有接口

Spring授课笔记3 - 图7

情况4:使用切面,不管是否有接口,都使用CGLIB

JDK动态代理:前提,必须有接口
CGLIB动态代理:有没有接口都可以

<aop:aspectj-autoproxy proxy-target-class=”true”/>
不管是否有接口,都是要CGLIB实现AOP
<aop:aspectj-autoproxy proxy-target-class=”false”/>
默认值,有接口使用JDK动态代理,没有接口使用CGLIB

Spring授课笔记3 - 图8

问题:为什么JDK动态代理必须有接口
因为JDK动态代理生成的代理类已继承了Proxy,而Java是单继承的,所以必须有接口。
Spring授课笔记3 - 图9

package com.atguigu.util;
import com.atguigu.service.CalculatorPureImpl;import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;import java.io.IOException;
public class Util {
public static void writeProxyClassToHardDisk(String path,Class[] clazz) {
byte[] classFile = ProxyGenerator.generateProxyClass(“$Proxy22”, clazz);
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Util.writeProxyClassToHardDisk(“d:/$Proxy22.class”, CalculatorPureImpl.class.getInterfaces());
}
}

问题2:为什么CGLIB实现动态代理不要求有接口

static{
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, “cglib”);
}

因为CGLIB生成的代理类没有固定的父类,所以可以直接继承目标类,不需要接口。

Spring授课笔记3 - 图10

6 AOP对IoC的影响

情况1: 没有AOP

使用接口和实现类都可以(前提,使用接口,接口只有一个实现类;使用实现类,实现类只能在SpringIoC容器中一个实例)

public class TestAOP2 {
@Autowired
//private Calculator calculator;
private CalculatorPureImpl calculator;
}

情况2: 使用AOP,但是使用JDK动态代理(Proxy)


使用接口可以,使用实现类不可以
Spring授课笔记3 - 图11

情况3: 使用AOP,但是使用CGLIB

使用接口、实现类都可以。
Spring授课笔记3 - 图12

7 AOP XML配置(了解)

目前Spring的开发以注解开发为主。XML配置基本不使用。

| <bean id=”calculatorPure” class=”com.atguigu.service.CalculatorPureImpl”></bean>

<bean id=”logAspect” class=”com.atguigu.aspect.LogAspect”></bean>

<aop:config>

<aop:pointcut id=”pointcut1” expression=”execution( com.atguigu.service..*(..))”/>

_<!--@Aspect --><br />    <!--@Order --><br />    _<**aop:aspect ref="logAspect" order="3"**><br />        _<!--@Before --><br />        _<**aop:before method="beforeLog" pointcut-ref="pointcut1"**></**aop:before**><br />        _<!-- @AfterReturning--><br />        _<**aop:after-returning method="afterSuccessLog" pointcut-ref="pointcut1" returning="result1"**></**aop:after-returning**><br />        _<!-- @AfterThrowing--><br />        _<**aop:after-throwing method="afterExceptionLog" pointcut-ref="pointcut1" throwing="ex"**></**aop:after-throwing**><br />        _<!--@After --><br />        _<**aop:after method="afterFinallyLog" pointcut-ref="pointcut1"**></**aop:after**><br />    </**aop:aspect**>        <br /></**aop:config**> |

| —- |

8 JDBCTemplate

Spring提供了整合JDBC的JDBCTemplate
因为先有Spring,再出现MyBatis,所以整合包不是有spring提供的,而是第三方提供的。

JDBCTemplate和MyBatis共同点:都是对JDBC进行了封装
不同点:肯定是MyBatis封装的更彻底,提供了更多的功能

9 添加依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>

Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包;导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入

10 进行配置

| <context:property-placeholder location=”classpath:db.properties”></context:property-placeholder>

<bean id=”dataSource” class=”com.alibaba.druid.pool.DruidDataSource”>
<property name=”driverClassName” value=”${jdbc.driver}”></property>
<property name=”url” value=”${jdbc.url}”></property>
<property name=”username” value=”${jdbc.user}”></property>
<property name=”password” value=”${jdbc.password}”></property>
</bean>

<bean id=”jdbcTemplate” class=”org.springframework.jdbc.core.JdbcTemplate”>
<property name=”dataSource” ref=”dataSource”></property>
</bean> | | —- |

11 测试

| @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = “classpath:spring.xml”)public class TestJDBCTemplate {
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;

@Test<br />    **public void **testConnection() **throws **SQLException {<br />        Connection connection = **dataSource**.getConnection();<br />        System.**_out_**.println(connection);<br />    }<br />} |

| —- |

12 具体操作

@Testpublic void testInsert() throws SQLException {
String sql = “insert into t_emp values(null,?,?)”;
jdbcTemplate.update(sql,“johnson”,400.45);
}@Testpublic void testUpdate() throws SQLException {
String sql = “update t_emp set emp_name = ? ,emp_salary = ? where emp_id = ?”;
jdbcTemplate.update(sql,“johnson2”,800.45,31);
}@Testpublic void testDelete() throws SQLException {
String sql = “delete from t_emp where emp_id = ?”;
jdbcTemplate.update(sql,31);
}@Testpublic void testQueryName() throws SQLException {
String sql = “select emp_name from t_emp where emp_id =1”;
String ename = jdbcTemplate.queryForObject(sql, String.class);
System.out.println(ename);
}
@Testpublic void testQueryEntity() throws SQLException {
String sql = “select * from t_emp where emp_id =1”;
//BeanPropertyRowMapper:按照驼峰命名规则进行映射 empid ——> empId emp_salary—>empSalary
RowMapper rowMapper = new BeanPropertyRowMapper(Employee.class);
Employee emp = jdbcTemplate.queryForObject(sql, rowMapper);
System.**_out
.println(emp);
}@Test
public void testQueryList() throws SQLException {
String sql =
“select * from temp”;
//BeanPropertyRowMapper:按照驼峰命名规则进行映射 emp_id ——> empId emp_salary—>empSalary
RowMapper rowMapper =
new BeanPropertyRowMapper(Employee.class);
List list =
jdbcTemplate.query(sql, (ResultSet rs, int var2)->{
Employee emp =
new Employee();
emp.setEmpId(rs.getInt(1));
emp.setEmpName(rs.getString(
“emp_name”));
emp.setSalary(rs.getDouble(
“emp_salary”));
return emp;
});
list.forEach((emp)-> System.
_out
**.println(emp));
}
@Testpublic void testCount() throws SQLException {
String sql = “select count(*) from t_emp”;
Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println(count);
}

13 声明式事务概念

事务的类型

  • 编程式事务
  • 声明式事务(xml声明、注解声明)

Spring的事务管理的API
Spring授课笔记3 - 图13
Spring授课笔记3 - 图14
Spring授课笔记3 - 图15
我们现在(使用JDBCTemplate访问数据库)要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
DataSourceTransactionManager类中的主要方法:

  • doBegin():开启事务
  • doSuspend():挂起事务
  • doResume():恢复挂起的事务
  • doCommit():提交事务
  • doRollback():回滚事务

    14 事务实验1:准备工作

    15 添加依赖

    和JdbcTemplate相同的依赖:必须有spring-orm。

    16 配置文件

    和JdbcTemplate相同的配置

    17 DAO层代码

    EmpDao

    18 业务代码

    EmpService

    19 测试代码

    Spring授课笔记3 - 图16
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = “classpath:spring.xml”)public class TestTransaction {
@Autowired
private EmpService empService;
@Test
public void testUpdate(){
empService.updateTwice(1,“tomcat”,2,800.0);
}
}

20 事务实验2:应用最基本的事务控制

21 配置事务管理器

<bean id=”transactionManager”
class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”
>
<property name=”dataSource” ref=”dataSource”></property>
</bean>

22 开启基于注解的声明式事务功能

<tx:annotation-driven transaction-manager=”transactionManager”></tx:annotation-driven>

Spring授课笔记3 - 图17

23 在需要事务的方法上使用注解(事务加载业务层)

Spring授课笔记3 - 图18

24 启用日志

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<?xml version=”1.0” encoding=”UTF-8”?><configuration debug=”true”>

<appender name=”STDOUT”
class=”ch.qos.logback.core.ConsoleAppender”
>
<encoder>


<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
_<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --><br />    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --><br />    _<**root level="INFO"**><br />        _<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --><br />        _<**appender-ref ref="STDOUT" **/><br />    </**root**>

_<!-- 根据特殊需求指定局部日志级别 --><br />    _<**logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" level="DEBUG"**/><br />    <**logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG" **/>

</configuration> |

成功提交事务
Spring授课笔记3 - 图19
失败回滚事务
Spring授课笔记3 - 图20

25 事务实验3:只读

| public @interface Transactional {
@AliasFor(“transactionManager”)
String value() default “”;

@AliasFor(**"value"**)<br />    String transactionManager() **default ""**;

String[] label() **default **{};

Propagation propagation() **default **Propagation.REQUIRED;

Isolation isolation() **default **Isolation.DEFAULT;

**int **timeout() **default **-1;

String timeoutString() **default ""**;

**boolean **readOnly() **default false**;

Class<? **extends **Throwable>[] rollbackFor() **default **{};

String[] rollbackForClassName() **default **{};

Class<? **extends **Throwable>[] noRollbackFor() **default **{};

String[] noRollbackForClassName() **default **{};<br />} |

| —- |

事务的四大特征:ACID 原子性 一致性 隔离性 持久化
Spring中事务的四大属性:
Propagation 传播特性 Isolation 隔离级别 timeout 超时时间 只读事务readonly 异常回滚类型
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
针对增删改操作设置只读,就会抛出异常:Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
问题:查询不使用事务, 还是使用只读事务??
应用场合:
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
————————————————
版权声明:本文为CSDN博主「renminzdb2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/renminzdb2/article/details/84616004
注解@Transactional的位置
类上:该类的所有方法都启用事务
方法上:只有该方法启用事务,和其他方法无关
如果类上和某些方法上都有事务,以方法上的事务配置为准。

26 事务实验4:超时

Spring授课笔记3 - 图21

Spring授课笔记3 - 图22

  1. 如果事务中后一个操作超时,事务会回滚,前一个操作也回滚。
  2. 如果超时,出现如下org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2021
  3. Thread.sleep()要注意位置,如果添加在上图的位置,即使超时,也不会回滚

    27 事务实验5:回滚异常类型

    默认只针对运行时异常回滚,编译时异常不回滚
    验证
    Spring授课笔记3 - 图23
    修改
@Transactional(timeout = 5,
rollbackFor = Exception.class,
noRollbackFor = SQLException.class
)
@Transactional(timeout = 5,
noRollbackFor = Exception.class,
rollbackFor = SQLException.class
)

案例:所有的异常都回滚
方法1:设置rollbackFor=Exception.class

@Transactional(timeout = 5,
rollbackFor = Exception.class
)

方法2:异常链。底层出现了检查异常,进行处理,并向上层抛出一个新的异常(一般是运行异常)。既可以传递异常到上层,还可以避免方法签名中使用throws。在Spring事务中还可以实现所有异常都回滚的效果

| public void updateEmpSalaryById(Integer empId, Double salary) {
String sql = “update t_emp set emp_salary=? where emp_id=?”;
jdbcTemplate.update(sql, salary, empId);

//int n = 10/0;<br />    //new FileInputStream("asdfadf");<br />    **try **{<br />        **new **FileInputStream(**"asdfadf"**);<br />    } **catch **(FileNotFoundException e) {<br />        e.printStackTrace();<br />        **throw new **RuntimeException(e.getMessage());<br />    }<br />} |

| —- |

28 事务实验4:隔离级别

1.数据库访问的并发问题

  • 脏读 :一个事务读到了另一个事务的未提交的数据
  • 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
  • 幻读 :一个事务读到了另一个事务已经提交的 insert或者delete 的数据导致多次查询结果不一致.

    2.通过设置不同的隔离级别解决数据库访问的并发问题

    | 隔离级别 | 脏读 | 不可重复读 | 幻读 | | —- | —- | —- | —- | | READ_UNCOMMITTED | √ | √ | √ | | READ_COMMITTED | × | √ | √ | | REPEATABLE_READ | × | × | √ | | SERIALIZABLE | × | × | × |

3.Spring的隔离级别

public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}

如果选择DEFAULT,默认值,由底层数据库自动判断应该使用什么隔离级别。
对于互联网高并发项目来说,如采用隔离级别SERIALIZABLE,固然安全,担心性能会受到严重影响。此时一般将隔离级别降低,保证效率,再配合悲观锁、乐观锁等技术保证安全性。

事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持。Oracle 支持的 2 种事务隔离级别:READ_COMMITED(默认) , SERIALIZABLE。MySQL 支持 4种事务隔离级别,默认REPEATABLE READ。
注意:mysql中使用了MVCC多版本控制技术,在REPEATABLE READ这个级别也可以避免幻读。

29 事务实验4:传播特性

在大多数情况下,我们会认为事务操作要么都成功,要么回到初始状态。比如下订单,如果主订单添加成功,三个订单明细添加成功两个,总额就对不上了,顾客最终收货会有减少的。这种情况肯定要保证全部成功。类似还有银行转账例子。但也存在不同的需求,
一般是业务层直接调用DAO层,不存在业务层平行调用的问题。哪怎么会有被调用的方法还是否开启事务的问题呢?比如:
①Service方法应用了通知
②过滤器或拦截器等类似组件

名称 含义
REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认值。
REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED 如当前存在事务,则在嵌套事务内执行。如当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作