Spring初窥门径

学习资料:Spring官方文档
Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。

一.IOC

当我们自己写一个简易的程序的时候,我们总会要new很多对象,但如果要写一个大项目呢,多个类之间互相耦合,关系复杂,不宜处理。这个时候,有人就想到了,搞一个容器,它负责所有的类的生成、注入、装配,来解除耦合,这就是IOC容器。

通俗来讲,就是将new实例这个过程,放到spring(IOC)容器里来做。当项目初始化的时候,spring(IOC)容器会先实例化要所有使用的类,然后需要用到这个实例的时候,从spring(IOC)容器中将这个实例注入即可。那么怎么告诉spring哪些类要实例化、怎么实例化呢?Spring可以通过xml或者注解获取这些信息。

二.XML注入

1.set注入

要求:有无参构造和set方法
解释:就是通过调用set方法,将对应的属性注入,可以直接注入值(property),也可以注入bean的引用(ref)或者一些集合。
样例:

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

    <bean id="address" class="com.pojo.Address">
        <property name="address" value="xian"></property>
    </bean>

    <bean id="student" class="com.pojo.Student">
        <property name="name" value="hou"/>
        <property name="address" ref="address"/>

        <!--数组注入-->
        <property name="books">
            <array>
                <value>三国</value>
                <value>西游</value>
                <value>水浒</value>
            </array>
        </property>

        <!--list-->
        <property name="hobbies">
            <list>
                <value>eat</value>
                <value>drink</value>
                <value>play</value>
            </list>
        </property>

        <property name="card">
            <map>
                <entry key="1" value="12"/>
                <entry key="2" value="23"/>
            </map>
        </property>

        <property name="game">
            <set>
                <value>wangzhe</value>
                <value>daota</value>
                <value>lol</value>
            </set>
        </property>

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

        <!--properties-->
        <property name="infor">
            <props>
                <prop key="id">20200405</prop>
                <prop key="name">hdk</prop>
            </props>
        </property>
    </bean>

</beans>

补充:bean中可以补充name设置别名(分隔符可以用逗号,空格或者分号),也可以直接设置别名

<bean id="usert" class="com.hou.pojo.UsetT" name="u1,u2">
    <property name="name" value="kun"/>
</bean>

<alias name="user" alias="user2aaa"/>

2.构造器注入

要求:有一个或多个有参构造
解释:就是通过spring的有参构造(constructor-arg),将对应的属性注入
样例:

<bean id="user" class="com.hou.pojo.User">
    <constructor-arg name="name" value="hou"></constructor-arg>
</bean>

3.p和c注入

要求:p的命名空间以及set注入要求 c的命名空间以及构造器注入要求
解释:p注入就是对set注入的一个简化封装,c注入就是对构造器注入的简化封装
可以直接通过"p:属性名"或者"c:属性名"的方式直接在bean里进行赋值,
样例:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--p命名空间注入/set注入-->
    <bean id="use" class="com.pojo.User" p:name="dong" p:age="10">
    </bean>

    <!--c命名空间/构造器-->
    <bean id="use2" class="com.pojo.User" c:name="kun" c:age="19" scope="prototype"></bean>
</beans>

补充:scope作用域
scope是bean里的一个属性,可以指定使用单例模式还是原型模式或者web中的session、request、application、websocket
单例就是只使用一个实例,原型就是每次注入的时候都new一个新的实例,默认是单例
也可以通过注解@scope实现

三.自动装配

将一个复杂的类的配置进行简化。
可以使用bean的autowire属性,以byName或者byType的方式进行装配。
但用注释进行装配会更加简单,这里只介绍注释自动装配。
@Autowired注释会先根据类型进行注入,如果有多个满足的类型,再根据name进行注入

注解装配条件:

  1. jdk1.5+
  2. spring2.5+
  3. context上下文:
    xmlns:context=“http://www.springframework.org/schema/context”
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
  4. spring-aop包
  5. @Autowired注释

<context:annotation-config/>可以让已经注册过的bean注册以下四种Processor,也就是可以使用以下四种Processor对应的注解。

  1. AutowiredAnnotationBeanPostProcessor
  2. CommonAnnotationBeanPostProcessor
  3. PersistenceAnnotationBeanPostProcessor
  4. RequiredAnnotationBeanPostProcessor

例子:

<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean id="cat111" class="com.pojo.Cat"/>
    <bean id="dog" class="com.pojo.Dog"/>

    <bean id="people" class="com.pojo.People">
    </bean>

</beans>
public class People {

    @Autowired
    private Cat cat;
  
    @Autowired
    @Qualifier(value = "dog")
    private Dog dog;
  
    private String name;

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", name='" + name + '\'' +
                '}';
    }

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

补充:
@Nullable:代表对象可以为null,放在类型前面
@Autowired(required=false):代表@Nullable
@Qualifier(value=""):当@Autowired环境很复杂时,手动指定装配的实例名称,配合@Autowired使用
@Resource:
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。

四.注解开发

约束:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--指定要扫描的包-->
    <context:component-scan base-package="com.pojo"/>

</beans>

<context:component-scan base-package="com.pojo"/>可以通过注解去注册bean,并包含了<context:annotation-config/>的所有功能!
按照不同的指责可以使用 组件@Component,控制器@Controller,DAO@Repository,服务@Service,代表这个类被Spring管理;
用@Value("")来对属性进行赋值,可以放在set方法或者属性上面。

package com.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

//等价于<bean id="user" class="com.pojo.User"></bean>
@Component
@Scope("prototype")
public class User {

    @Value("dong")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

XML与注解比较
–XML可以适用任何场景 ,结构清晰,维护方便
–注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践
–xml管理Bean
–注解完成属性注入
–使用过程中, 可以不用扫描,扫描是为了类上的注解

补充:
使用JavaConfig的方式配置Spring,可以用一个配置类代替xml配置

写一个配置类

package com.config;

import com.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration //这个也会被spring容器托管,注册到容器中,因为他本来就是一个@Component
@ComponentScan("com.pojo")
@Import(Config2.class)
public class MyConfig {

    @Bean
    public User getUser(){
        return new User();
    }

}

将加载xml上下文改成加载config上下文

import com.config.MyConfig;
import com.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Mytest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = (User) context.getBean("getUser");
        System.out.println(user);
    }
}

五.AOP

面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP可以在不改变原有代码的基础上,通过动态代理从切面增加新的功能。

主流的动态代理方式有两种:java原生 和 cglib

java原生的动态代理需要实现接口,cglib不用

springboot2.0之前默认使用javaJDK的动态代理,2.0以后默认cglib,可以通过手动配置去修改默认的配置。

JDK的动态代理需要了解两个类 : InvocationHandler 和 Proxy

1.InvocationHandler & Proxy

InvocationHandler:调用处理程序

每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用的invoke方法。

看例子里面的Client中调用proxy.rent(),相当于就是通过反射,将rent方法的信息作为method参数,调用了代理类中的invoke方法。

Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理的超类。

自己写一个代理类继承InvocationHandler,重写invoke方法,
写一个getProxy方法,通过Proxy的newProxyInstance静态方法获取proxy实例

package com;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//会这个类,自动生成代理类
public class ProxyInvocation implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces()
        ,this);
    }

    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    public void seeHouse(){
        System.out.println("see house");
    }

    public void fare(){
        System.out.println("fare");
    }
}

package com;

public class Client {

    public static void main(String[] args) {
        //真实角色
        Host host = new Host();

        //代理角色
        ProxyInvocation proxyInvocation = new ProxyInvocation();

        //通过调用程序处理角色来处理我们要调用的接口对象
        proxyInvocation.setRent(host);

        Rent proxy = (Rent) proxyInvocation.getProxy();  //这里的proxy是动态生成的

        proxy.rent();

    }

}

2.Spring中的AOP

spring使用的是最广泛的aspectJ

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  1. 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  2. 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  3. 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  4. 目标(Target):被通知对象。
  5. 代理(Proxy):向目标对象应用通知之后创建的对象。
  6. 切入点(PointCut):切面通知 执行的 “地点”的定义。
  7. 连接点(JointPoint):与切入点匹配的执行点
  8. 切点函数(execution):(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?

提供五种类型的Advice:

  1. 前置通知 继承MethodBeforeAdvice @Before
  2. 后置通知 继承AfterReturningAdvice(有返回值)或继承AfterAdvice(无返回值) 重写after Returning或after方法 @After @AfterReturning
  3. 环绕通知 @Around
  4. 异常抛出通知 @AfterThrowing
  5. 引介通知(类中增加新方法)

依赖:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

和IOC一样,都可以使用配置文件或者注解实现

方法一:配置文件

public class Log implements MethodBeforeAdvice {

   //method : 要执行的目标对象的方法
   //objects : 被调用的方法的参数
   //Object : 目标对象
   @Override
   public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
  }
}
public class AfterLog implements AfterReturningAdvice {
   //returnValue 返回值
   //method被调用的方法
   //args 被调用的方法的对象的参数
   //target 被调用的目标对象
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
       System.out.println("执行了" + target.getClass().getName()
       +"的"+method.getName()+"方法,"
       +"返回值:"+returnValue);
  }
}
<?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: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/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--注册bean-->
   <bean id="userService" class="com.kuang.service.UserServiceImpl"/>
   <bean id="log" class="com.kuang.log.Log"/>
   <bean id="afterLog" class="com.kuang.log.AfterLog"/>

   <!--aop的配置-->
   <aop:config>
       <!--切入点 expression:表达式匹配要执行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
   </aop:config>

</beans>

也可以将advice写入配置文件里,这样自己定义的切片类就不用继承五个advice了。

第一步 : 写我们自己的一个切入类

public class DiyPointcut {

   public void before(){
       System.out.println("---------方法执行前---------");
  }
   public void after(){
       System.out.println("---------方法执行后---------");
  }
   
}

去spring中配置

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
   <!--第二种方式:使用AOP的标签实现-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

测试:

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
  }
}

方法二:注解实现

注解里的value详解:

execution(* com.kuang.service.UserServiceImpl.*(…)) 方法执行处

从左到右依次是:

  1. execution(): 表达式主体。

  2. 第一个*号:表示返回类型,*号表示所有的类型。

  3. 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法,*号表示所有的类。

  4. *(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

call 方法调用处

和上面的区别可以看这个例子

截屏2021-12-02 上午6.35.47

如果用execution拦截main方法,会拦截第11行

如果用call拦截main,会拦截第8行

常用方法汇总

方法执行:execution(MethodSignature)

方法调用:call(MethodSignature)

构造器执行:execution(ConstructorSignature)

构造器调用:call(ConstructorSignature)

类初始化:staticinitialization(TypeSignature)

属性读操作:get(FieldSignature)

属性写操作:set(FieldSignature)

例外处理执行:handler(TypeSignature)

对象初始化:initialization(ConstructorSignature)

对象预先初始化:preinitialization(ConstructorSignature)

Advice执行:adviceexecution()

切入点指示符

切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下:

execution:用于匹配方法执行的连接点;

within:用于匹配指定类型内的方法执行;

this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;

target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;

args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;

@within:用于匹配所有持有指定***注解***类型内的方法;

@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的***注解***;

@args:用于匹配当前执行的方法传入的参数持有指定***注解***的执行;

@annotation:用于匹配当前执行方法持有指定***注解***的方法;

package com.kuang.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AnnotationPointcut {
   @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
   public void before(){
       System.out.println("---------方法执行前---------");
  }

   @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
   public void after(){
       System.out.println("---------方法执行后---------");
  }

   @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
   public void around(ProceedingJoinPoint jp) throws Throwable {
       System.out.println("环绕前");
       System.out.println("签名:"+jp.getSignature());
       //执行目标方法proceed
       Object proceed = jp.proceed();
       System.out.println("环绕后");
       System.out.println(proceed);
  }
}

接着:在Spring配置文件中,注册bean,并增加支持注解的配置

<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

补充重点

在切面里无法autowired获取bean,想要使用spring容器中的组件,需要使用通过BeanFactory获取bean或者懒加载需要使用的bean!!!具体使用方法可以参考若依里面的SpringUtils。

通过实现接口BeanFactoryPostProcessor可以获取beanFactory;

通过实现接口ApplicationContextAware可以获取applicationContext;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 * 
 * @author ruoyi
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
    {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws org.springframework.beans.BeansException
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) AopContext.currentProxy();
    }

    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */
    public static String[] getActiveProfiles()
    {
        return applicationContext.getEnvironment().getActiveProfiles();
    }

    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */
    public static String getActiveProfile()
    {
        final String[] activeProfiles = getActiveProfiles();
        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
    }
}

六.SpringBoot集成MyBatis

spring需要在xml中配置dataSource、sqlSessionFactory

不过现在都用springboot了,就直接讲一下springboot的yml常用配置吧~

# MyBatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: com.example.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml

typeAliasesPackage可以允许我们在mapper中,resultType只写类名,不需要写完整的com.**的路径

mapperLocations是我们需要扫描的mapper位置,通过通配符可以从不同的地方拿到mapper.xml

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

七.SpringBoot配置事务

参考博文:https://blog.csdn.net/wkl305268748/article/details/77619367

简介以及使用

在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。
编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单。

  1. 通过@Transactional,实现了事务操作。
  2. Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,因此对于我们自定义异常,通过rollbackFor进行设定
  3. 如果我们需要捕获异常后,同时进行回滚,通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();进行手动回滚操作。
  4. 使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
    设置回滚点,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint。

注解参数

  • readOnly:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)
  • rollbackFor:该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
  • rollbackForClassName 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})
    noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
  • noRollbackForClassName 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})
  • propagation 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
  • isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
  • timeout 该属性用于设置事务的超时秒数,默认值为-1表示永不超时

事务属性

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。

TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。

TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性

只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值