Spring基础

Spring基础

1、Spring概念

Spring是分层的全栈式轻量级开发框架,以IOC和AOP为核心

1.1、Spring核心的四个依赖

  • spring-context:上下文,容器
  • spring-beans:创建对象
  • spring-core:核心jar
  • spring-expression:表达式jar

实际开发中,只需要导入spring-context依赖就行,因为spring-context依赖引用了其他依赖。
spring-context依赖

1.2、Spring体系结构

Spring体系结构

2、Spring IOC

2.1、IOC概念

IOC(Inversion of Control):控制反转

简单来说,就是把Java创建对象的权力交给Spring容器,由Spring容器控制对象的创建。

2.2、IOC原理

  1. 通过xml解析,读取xml配置文件。(或通过注解解析,读取注解相关信息)
  2. 通过反射实例化对象,放入Map容器中,key为xml配置中 的 id值,value为对应的对象
  3. 通过工厂模式的getBean方法,返回bean对象

3、通过XML配置和注解方式实现DI(依赖注入)

3.1、XML实现DI

bean标签简介
<bean id="user" class="com.cl.bean.User"
      lazy-init="false" scope="prototype">
<bean>

class: 类的全路径名
lazy-init: 是否开启懒加载(调用getBean的时候再去实例化对象)
scope:
1、singleton(容器中存放一个共享的bean实例,即单例)
2、prototype(每一次获取bean对象就会new一个新的bean)
3、request(针对每一次bean都会产生新的bean,同时该bean只对当前Http request有效)
4、session(针对每一次bean都会产生新的bean,同时该bean只对当前Http session有效)

3.1.1、通过set方法

property标签:是使用set方法进行依赖注入

<bean id="user" class="com.cl.bean.User">
    <property name="id" value="1"></property>
    <property name="name" value="张三"></property>
    <property name="pwd" value="123456"></property>
</bean>
3.1.2、通过构造器注入

constructor-arg标签:使用构造器实现依赖注入

<!-- 通过参数名(name)注入 -->
<bean id="user" class="com.cl.bean.User">
    <constructor-arg name="id" value="2"></constructor-arg>
    <constructor-arg name="name" value="李四 "></constructor-arg>
    <constructor-arg name="pwd" value="123"></constructor-arg>
</bean>
<!-- 通过参数索引(index)注入 -->
<bean id="user3" class="com.cl.bean.User">
    <constructor-arg index="0"  value="3"></constructor-arg>
    <constructor-arg index="1"  value="王五"></constructor-arg>
    <constructor-arg index="2"  value="321"></constructor-arg>
</bean>
3.1.3、通过p空间和c空间给对象赋值

首先添加约束:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

p空间类似property:通过set方法注入

<!--p名称空间,就是对property的简化处理-->
<bean id="user" class="com.cl.bean.User" p:id="4" p:name="赵六" p:pwd="111" ></bean>

c空间类似constructor-arg:通过构造器注入

 <!--c名称空间,就是对constructor-arg的简化--> 
 <bean id="user" class="com.cl.bean.User" c:id="5" c:name="王七" c:pwd="222" ></bean>
3.1.4、注入空值和特殊符号

注入空值:

 <property name="id">
      <null></null>
 </property>

注入转义字符:
&lt; :<
&gt; :>
&amp; :&

<property name="username" value="&amp;张三&lt;&gt;"></property>

注入特殊字符:

<![CDATA[内容]]>
<property name="pwd">
    <value><![CDATA[&<123456>]]></value>
</property>

通过ref引用外部bean:

<bean id="date" class="java.util.Date"></bean>

<bean id="user" class="com.cl.bean.User">
    <property name="name" value="李四"></property>
    <!--bean引用引用外部bean-->
    <property name="birthdate" ref="date"></property>
</bean>

集合注入:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
">

  <bean id="student" class="com.cl.bean.Student">
        <!--数组属性注入-->
        <property name="bookArray">
            <array>
                <value>JAVA</value>
                <value>MySQL</value>
                <value>Spring</value>
            </array>
        </property>
        <!--set集合注入-->
        <property name="bookSet">
            <set>
                <value>JAVA</value>
                <value>MySQL</value>
                <value>Spring</value>
            </set>
        </property>
        <!--list集合注入-->
        <property name="bookList">
            <list>
                <value>JAVA</value>
                <value>MySQL</value>
                <value>Spring</value>
            </list>
        </property>
        <!--map集合注入-->
        <property name="bookMap">
            <map>
                <entry key="JAVA" value="java"></entry>
                <entry key="Go" value="go"></entry>
                <entry key="JVM" value="jvm"></entry>
            </map>
        </property>
        <!--List对象集合注入-->
        <property name="bookObjectList" ref="b1"></property>
    </bean>
    
	<bean id="b1" class="com.cl.bean.Book" p:bname="JAVA" p:author="cl"></bean>
</bean>

3.2、注解方式实现DI

3.2.1、类上依赖注入

@Component:告诉Spring当前类需要实例化至容器中

@Component有三大子注解:

  • @Controller:注解Controller控制层Bean
  • @Service:注解Service服务层Bean
  • @Repository:注解Repository持久层Bean
3.2.2、属性上依赖注入

@Autowired 根据属性数据类型自动装配
@Qualifier 根据属性名称注入依赖
@Resources 可以根据类型,也可以根据名称注入
@Value 注入普通数据类型(8+String)

4、Bean的生命周期

Bean的生命周期

4.1、Spring循环依赖问题

循环依赖产生:

循环依赖

4.2、Spring三级缓存解决循环依赖

具体请看视频讲解:循环依赖

ClassA:

@Component
public class ClassA {
    
    @Autowired
    private ClassB classB;

    @Autowired
    private ClassC classC;
    
    public void testA(){
        System.out.println("testA ");
    }
    
}

ClassB:

@Component
public class ClassB {
    
    @Autowired
    private ClassA classA;
    
    public void testB(){
        System.out.println("testB");
    }
}

ClassC:

@Component
public class ClassC {
    
    @Autowired
    private ClassA classA;
    
    public void testC(){
        System.out.println("testC");
    }
}

特殊情况:ClassA出现循环依赖时 ⇒ 对ClassA提前AOP产生代理对象

classA正在创建中(说明出现循环依赖):creatingSet集合存放正在创建的对象名。

classA的生命周期

利用creatingSet.add(“classA”)

  1. 实例化 ⇒ ClassA不完整对象 ⇒ 放入第三级缓存<“classA”, classA原始对象、beanName、beanDefinition>

  2. 填充classB属性 ⇒ 从单例池中(singletonObjects)找classB ⇒ 找不到 ⇒ 创建ClassB

    classB的生命周期

    1. 实例化 ⇒ ClassB对象 ⇒ 放入二级缓存池中
    2. 填充classA属性 ⇒ 从单例池中(singletonObjects)找classA ⇒ 找不到 ⇒ classA正在创建中(说明出现循环依赖) ⇒ 从二级缓存中找 【找不到的话特殊情况提前AOP ⇒ 第三级缓存 ⇒ 执行lambda表达式,判断要不要AOP产生ClassA代理对象(此时不完整,不能放入单例池中) ⇒ 放入二级缓存中】
    3. 填充其他属性
    4. 做其他事
    5. 放入单例池

    classC的生命周期

    1. 实例化 ⇒ ClassC对象 ⇒ 放入二级缓存池中
    2. 填充classA属性 ⇒ 从单例池中(singletonObjects)找classA ⇒ 找不到 ⇒ classA正在创建中(说明出现循环依赖) ⇒ 从二级缓存中找 【找不到的话特殊情况提前AOP ⇒ 第三级缓存 ⇒ 执行lambda表达式,判断要不要AOP产生ClassA代理对象(此时不完整,不能放入单例池中) ⇒ 放入二级缓存中】
    3. 填充其他属性
    4. 做其他事
    5. 放入单例池
  3. 填充其他属性

  4. 做其他事 ⇒ AOP ⇒ ClassA代理对象
    4.1 从二级缓存中取代理对象

  5. 放入单例池

利用creatingSet.remove(“classA”)

第一级缓存:单例池 ⇒ singleObjects ConcurrentMap <beanName, bean完整对象>
第二级缓存(解决多例问题,保证bean不完整对象单例): earlySingleObjects ConcurrentHashMap <beanName, 唯一的bean不完整对象>
第三级缓存:singleObjects HahMap <beanName, ObjectFactory<?>>

5、Bean的自动装配

  • autowire:属性控制自动将容器中的对象注入到当前对象的属性上
  • byType:根据类型注入,要保证当前bean在容器中只有一个实例
  • byName:根据id和属性名称注入,要保证id和属性名称一致

6、外部属性配置文件的使用

  1. xml方式:通过context命名空间读取属性配置文件
    (并且通过${},引用配置文件的内容)
  • jdbc.properties
jdbc_username=root
jdbc_password=root
jdbc_url=jdbc:mysql://127.0.0.1:3306/test_db&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_driver=com.mysql.cj.jdbc.driver
  • application.xml
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
   <property name="username" value="${jdbc_username}"></property>
   <property name="password" value="${jdbc_password}"></property>
   <property name="url" value="${jdbc_url}"></property>
   <property name="driverClassName" value="${jdbc_driver}"></property>
</bean>
  1. 通过注解方式引入
@Configuration // 配置类标志
@ComponentScan(basePackages = "com.cl") // 注解扫描
@PropertySource("classpath:jdbc.properties") // 导入配置文件
@EnableTransactionManagement // 开启事务注解
public class SpringConfig {
    
   @Value("${jdbc_username}")
   private String username;
   
   @Value("${jdbc_password}")
   private String password;
   
   @Value("${jdbc_url}")
   private String url;
   
   @Value("${jdbc_driver}")
   private String driver;
   
   @Bean
   public DruidDataSource getDruidDataSource(){
       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setUsername(username);
       dataSource.setPassword(password);
       dataSource.setUrl(url);
       dataSource.setDriverClassName(driver);
       return dataSource;
   }
   
   @Bean
   public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
       JdbcTemplate jdbcTemplate = new JdbcTemplate();
       jdbcTemplate.setDataSource(dataSource);
       return jdbcTemplate;
   }
   
   @Bean
   public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource){
       DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
       transactionManager.setDataSource(dataSource);
       return transactionManager;
   }
   
}

7、JDK动态代理(面向接口)

通过代理对象访问控制目标对象。

JDK动态代理模式特点:
1、生成的代理对象只能转成接口类,因为无法确认被代理类具体是哪个
2、不能增强接口类中不存在的方法,实现类独有的方法是无法增强的
3、代理对象只能获取接口方法上的注解,不能获取实现类方法上的注解

public class ProxyTest {
    
    public static void main(String[] args) {
        final PlayGames playGames = new Person();
//        通过Proxy动态代理获得Dinner代理对象的类加载器,并对Dinner接口方法进行加强
        ClassLoader loader = playGames.getClass().getClassLoader();
//        获取被代理对象的所有实现接口
        Class<?>[] interfaces = playGames.getClass().getInterfaces();
//        InvocationHandler 执行处理器对象,专门用于定义增强的规则
          InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * 
             * @param proxy 代理对象
             * @param method 代理对象访问的代理方法
             * @param args 参数
             * @return 返回值
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                if(method.getName().equals("play")){
                    System.out.println("打游戏前打开电脑");
                    result = method.invoke(playGames, args);
                    System.out.println("打游戏后关闭电脑");
                }else {
                    result = method.invoke(playGames, args);
                }
                return result;
            }
        };
        
        PlayGames dinnerProxy = (PlayGames)Proxy.newProxyInstance(loader, interfaces, invocationHandler);
        dinnerProxy.close();
    }
    
}


interface PlayGames{
    void play();
    void close();
}

class Person implements PlayGames{

    public void play() {
        System.out.println("张三正在打游戏");
    }

    public void close() {
        System.out.println("张三关闭电脑");
    }

}

class Student implements PlayGames{

    public void play() {
        System.out.println("学生正在打游戏");
    }

    public void close() {
        System.out.println("学生关闭电脑");
    }

}

事件处理器:在代理对象调用代理方法时,才会走invoke。

此时,调用了dinnerProxy.close();
proxy:代理对象,Person
method:PlayGames类中close的Method对象
args:传参为空
返回值为空

代理对象

8、CGLIB动态代理(面向父类)

CGLIB动态代理特点:
1、和接口没有直接关系
2、不仅可增强接口的方法,还可以增强类中的其他方法
3、可读取父类中的所有注解

public class CGlibTest {

    public static void main(String[] args) {
        Person person = new Person();
        // 1、获取Enhancer对象
        Enhancer enhancer = new Enhancer();
        // 2、设置父类字节码
        enhancer.setSuperclass(person.getClass());
        // 3、获取MethodIntercept对象,用于增强规则
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                if(method.getName().equals("play")){
                    System.out.println("打游戏前打开电脑");
                    result = methodProxy.invokeSuper(o, objects);
                    System.out.println("打游戏后关闭电脑");
                }else{
                    result = methodProxy.invokeSuper(o, objects);

                }
                return result;
            }
        };
        // 4、设置methodIntercept
        enhancer.setCallback(methodInterceptor);
        // 5、获取代理对象
        Person personProxy = (Person) enhancer.create();
        // 6、使用代理对象完成功能
        personProxy.play();
    }
    
}

class Person{
    public Person(){
        
    }
    
    public void play(){
        System.out.println("张三正在打游戏");
    }
    
}

9、AOP(面向切面编程)

AOP实现原理:动态代理
(有接口用JDK,没有用CGLIB)

9.1、功能

  • 日志处理
  • 权限控制
  • 性能检测
  • 事务控制

9.2、AOP术语

  1. JointPoint(连接点):类中可以被增强的方法。
  2. Pointcut(切入点):实际被增强的方法。
  3. Advice(通知):实际增强的功能(实际上添加的增强代码逻辑)。
  4. Target(目标对象):被代理的对象。
  5. Aspect(切面):一个java类,其中包括许多advice方法。
  6. Weaving(织入):创建代理对象并实现增强功能的声明并运行的过程。

9.3、AOP实现

导入依赖:

 <!--spring切面包-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aspects</artifactId>
     <version>5.3.5</version>
 </dependency>
 <!--织入包  spring-aspects 已经导入该包,这里可以不导入-->
 <!--<dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.9.6</version>
 </dependency>-->
 <!--aop联盟包-->
 <dependency>
     <groupId>aopalliance</groupId>
     <artifactId>aopalliance</artifactId>
     <version>1.0</version>
 </dependency>
9.3.1、基于xml实现(了解)
9.3.1.1、application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop.xsd
">

    <context:component-scan base-package="com.cl"></context:component-scan>        
<!--    aop autoProxy自动生成代理对象-->
    <aop:aspectj-autoproxy/>
    
<!--    配置文件配置aop增强功能-->
    <aop:config>
<!--        配置切入点-->
        <aop:pointcut id="pointCut" expression="execution(* com.cl.dao.UserDao.addUser(..))"></aop:pointcut>
        <aop:aspect ref="daoAspect">
            <aop:before method="before" pointcut-ref="pointCut"></aop:before>
            <aop:after method="after" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="res"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
            <aop:around method="around" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>
9.3.1.2、daoAspect
@Aspect
@Component
//@Order(2)
public class DaoAspect {

//    @Pointcut("execution(* com.cl.dao.UserDao.addUser(..))")
    public void pointCut(){}
    
//    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("before");
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
    }

//    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex){
        System.out.println("afterThrowing");
    }
    
//    @AfterReturning(value = "pointCut()", returning = "res")
    public void afterReturning(JoinPoint joinPoint, Object res){
        System.out.println("afterReturning");
        System.out.println(res.toString());
    }
    
//    @After(value = "pointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("after");
    }
    
//    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("aroundA");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("aroundB");
        return proceed;
    }
    
}
9.3.1.3、运行结果

AOP中xml方式运行结果

9.4.1、基于注解实现,以某个或某些方法为切入点(掌握)
9.4.1.1、切入点表达式和语法结构

Pointcut(切入点)表达式: 通过表达式来确定AOP要增强的是哪个或者哪些方法

语法结构: execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )

例子:

  • @Pointcut(execution(* com.msb.dao.UserDaoImpl.add(…))) // 指定切点为UserDaoImpl.add方法
  • @Pointcut(execution(* com.msb.dao.UserDaoImpl.*(…))) // 指定切点为UserDaoImpl.所有的方法
  • @Pointcut(execution(* com.msb.dao.*(…))) // 指定切点为dao包下所有的类中的所有的方法
  • @Pointcut(execution(* com.msb.dao.*.add(…))) // 指定切点为dao包下所有的类中的add的方法
  • @Pointcut(execution(* com.msb.dao.add*(…))) // 指定切点为dao包下所有的类中的add开头的方法
9.4.1.2、通知
  • @Before:前置通知

例子:@Before(“pointCut()”)

  • @After:后置通知

例子:@After(value = “pointCut()”)

  • @AfterReturning:返回通知

例子:@AfterReturning(value = “pointCut()”, returning = “res”),无异常,才走此通知。
res参数:返回值

  • @AfterThrowing:异常通知

例子:@AfterThrowing(value = “pointCut()”, throwing = “ex”),有异常,才走此通知
ex参数:异常

  • @Around:环绕通知

例子:@Around(value = “pointCut()”)

执行顺序:

@Around、 @Before、(无异常执行@AfterReturning、有异常执行@AfterThrowing)、@After、@Around

9.4.1.3、DaoAspect2
@Aspect
@Component
//@Order(1)
public class DaoAspect2 {

    @Pointcut("execution(* com.cl.dao.UserDao.addUser(..))")
    public void pointCut(){}
    
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("before2");
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
    }

    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex){
        System.out.println("afterThrowing2");
    }
    
    @AfterReturning(value = "pointCut()", returning = "res")
    public void afterReturning(JoinPoint joinPoint, Object res){
        System.out.println("afterReturning2");
        System.out.println(res.toString());
    }
    
    @After(value = "pointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("after2");
    }
    
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("aroundA2");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("aroundB2");
        return proceed;
    }
    
}
9.4.1.4、运行结果

AOP基于注解的运行结果

9.4.2、基于注解实现,以自定义注解切入点(掌握)

自定义注解:

  • @Documented:生成javadoc文档时保留此注解
  • @Target({ElementType.METHOD}):可以注解在哪些上面(这里可注解到方法中,具体可看ElementType这个枚举类)
  • @Retention(RetentionPolicy.RUNTIME):保留原则(这里是JVM运行时保留此注解)

自定义注解

9.4.2.1、DaoAspectAnno
@Aspect
@Component
public class DaoAspectAnno {

    @Pointcut("@annotation(com.cl.anno.AnnoPointCut)")
    public void pointCut(){}
    
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("annotation-before");
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
    }

    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex){
        System.out.println("annotation-afterThrowing");
    }
    
    @AfterReturning(value = "pointCut()", returning = "res")
    public void afterReturning(JoinPoint joinPoint, Object res){
        System.out.println("annotation-afterReturning");
        System.out.println("返回结果:" + res.toString());
    }
    
    @After(value = "pointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("annotation-after");
    }
    
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("annotation-aroundA");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("annotation-aroundB");
        return proceed;
    }
    
}
9.4.2.2、测试代码和效果

测试代码:

@Test
public void testAspectAnno(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    EmpService empService = applicationContext.getBean(EmpService.class);
    System.out.println(empService.addEmp(1810923, "李注解"));
}

empService.addEmp调用链:

在这里插入图片描述

测试效果:

在这里插入图片描述

10、事务

10.1、事务的概念

事务是一个操作序列:要么全都操作,要么全不操作。

10.2、事务的特性

  • 原子性:事务是最小执行单位,不可再分。
  • 一致性:事务从一致的状态变为另一个一致的状态。
  • 隔离性:事务之间互不影响。
  • 持久性:事务一旦提交,对数据的任何操作,可持久存储到硬盘中(持久化存储)。

10.3、事务的并发问题

  • 脏读: 一个事务读取到另一个事务已修改单未提交的数据。
  • 不可重复读: 一个事务多次读取同一数据,在此期间另一事务修改了数据,导致多次读取的结果不一致。
  • 幻读: 一个事务多次读取多行数据,在此期间另一事务增加或删除了数据,导致数据会突然增多或减少,像出现幻觉一样。

10.4、事务并发问题的解决:事务隔离级别

事务的隔离级别从低到高依次: READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ以及SERIALIZABLE
(注意:隔离级别越低,越能支持高并发的数据库操作。)

隔离级别脏读不可重复读幻读
READ UNCOMMITTED(读未提交)存在存在存在
READ COMMITTED(读已提交)不存在存在存在
REPEATABLE READ(可重复度)不存在不存在存在
SERIALIZABLE(串行化)不存在不存在不存在

10.5、注解方式控制事务

Spring声明式事务的实现方式,底层就是AOP,AOP的底层就是动态代理。

10.5.1、@Transactional

注解在类上,表示类中所有方法都加了事务控制。
注解在方法上,表示仅对当前方法加了事务控制。

10.5.1.1、@Transactional参数——propagation

propagation:事务传播级别

事务的传播级别说明
PROPAGATION_REQUIRED如果当前事务不存在,则新建事务,否则加入当前事务
PROPAGATION_REQUIRES_NEW新建当前事务,如果当前事务存在,则将当前事务挂起
PROPAGATION_MANDATORY使用当前事务,如果不存在,则抛出异常
PROPAGATION_SUPPORT支持当前事务,如果当前事务不存在,则以非事务方式执行
PROPAGATION_NOT_SUPPORT以非事务方式执行,如果当前事务存在,则将当前事务挂起
PROPAGATION_NEVER以非事务方式执行,如果当前事务存在,则抛出异常
PROPAGATION_NESTED如果当前事务存在,则嵌套事务中执行。否则执行与PROPAGATION_REQUIRED类似的操作
10.5.1.2、@Transactional参数——isolation
隔离级别说明
DEFAULT(默认)使用数据库默认的隔离级别,MySQL默认是REPEATABLE_READ,Oracle默认是READ_COMMITTED
READ_UNCOMMITTED(读未提交 )允许一个事务读取另一个事务已修改但未提交的数据
READ_COMMITTED(读已提交)保证一个事务只能读取到事务已提交的数据
READ_UNCOMMITTED(可重复读)除了保证一个事务只能读取已提交的数据,也保证了可重复读
SERIALIZABLE(串行化)事务最高隔离级别,保证事务处理是顺序执行的,避免了脏读、不可重复读和幻读,同时也是效率最低的
10.5.1.3、@Transactional参数——timeout

如果事务提交超出该时间,则回滚。

10.5.1.4、@Transactional参数——readOnly

事务是否只能读取数据,如果true,表示不能增删改。

10.5.1.5、@Transactional参数——rollbackFor

指定发生什么异常才回滚事务。

10.5.1.6、@Transactional参数——noRollbackFor

指定发生什么异常,事务不会回滚。

10.5.2、xml方式控制事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--    注解扫描-->
    <context:component-scan base-package="com.cl"></context:component-scan>
<!--    导入properties配置文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--    配置druid(德鲁伊)数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc_username}"></property>
        <property name="password" value="${jdbc_password}"></property>
        <property name="url" value="${jdbc_url}"></property>
        <property name="driverClassName" value="${jdbc_driver}"></property>
    </bean>
<!--    配置jdbcTemplate,并注入DataSource数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--    配置事务管理器器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--    开启事务注解驱动-->
    <tx:annotation-driven/>
</beans>
10.5.3、全注解方式控制事务
@Configuration // 配置类标志
@ComponentScan(basePackages = "com.cl") // 注解扫描
@PropertySource("classpath:jdbc.properties") // 导入配置文件
@EnableTransactionManagement // 开启事务注解
public class SpringConfig {
     
    @Value("${jdbc_username}")
    private String username;
    
    @Value("${jdbc_password}")
    private String password;
    
    @Value("${jdbc_url}")
    private String url;
    
    @Value("${jdbc_driver}")
    private String driver;
    
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
    
}

10.6、Spring的日志框架和测试

Spring5日志框架,官方建议使用log4j2
Spring5测试支持,支持junit4、整合了junit5

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值