Spring入门

1. Spring入门

首先要了解一下什么是spring。通过一个简单的样例来分析入门。

首先的自学必备,官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/

1.1 Spring简介

  • spring框架是以interface21框架为基础,重新设计丰富的成果。
  • spring就是整合了很多框架的一个工具
  • spring是一个免费的开源框架(容器)。
  • spring是一个轻量级的,非侵入式的框架。
  • spring核心:可控制反转(IOC),面向切面(AOP)。
  • 支持事务的处理。

spring这么牛逼,官网也这样介绍的:https://spring.io/why-spring。
反正一起吹spring牛逼就对了。

官方文档的介绍顺序是:

titlecontent解释
Overviewhistory, design philosophy, feedback, getting started.概述:讲一些历史,设计理念什么的
CoreIoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.核心:讲IOC和AOP,事件和配置
TestingMock Objects, TestContext Framework, Spring MVC Test, WebTestClient.测试:MVC
Data AccessTransactions, DAO Support, JDBC, O/R Mapping, XML Marshalling.数据访问:事务,JDBC,XML
Web ServletSpring MVC, WebSocket, SockJS, STOMP Messaging.WebServlet:SpringMVC等内容
Web ReactiveSpring WebFlux, WebClient, WebSocket.Web:WenSocket等内容
IntegrationRemoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching.集成:邮件,任务调度,缓存
LanguagesKotlin, Groovy, Dynamic Languages.语言:动态语言什么的

概述我们大概掠过。

打开官方文档的Core,一开始就是IOC容器介绍。(由于都是英文,我英语又不好,就翻译成中文看了)下面就不放英文原文了,有兴趣的同学可以去自己看一下。

1.2 环境配置

没什么环境要配的,轻量级的spring只需要导一下maven依赖就好了。

直接导webmvc的,一次到位

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

和一个整合:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>test</scope>
</dependency>

1.3 简单入门

我们首先进行一个简单的小样例,然后通过样例来分析spring。

通常情况下我们的三层实现是这样的:

  • pojo一个实体类。
  • dao实现接口和方法
public class UserDaoImpl implements UserDao {
    public void findAll() {
        System.out.println("dao实现");
    }
}
  • service层调用dao层
import com.admin.dao.UserDao;
import com.admin.dao.UserDaoImpl;

public class UserServiceImpl implements UserService {
    UserDao userDao = new UserDaoImpl();
    public void findAll() {
        userDao.findAll();
    }
}
  • 用户层调用service层:
import com.admin.service.UserService;
import com.admin.service.UserServiceImpl;
public class MyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.findAll();
    }
}

这样的三层架构,使得程序的耦合性非常高。用new 的方法创建对象,需要对象的实现类。修改任何一个实现,我们都需要将所有的代码修改好,不小心还可能出现很多的bug。

为了避免这样的情况,需要换一种思路,这种思路就是IOC思想,利用IOC思想来实现程序,通过set方法来实现修改参数。(IOC思想后面在讨论)

上面的可修改为:

  • dao层不变。
  • service修改为:
import com.admin.dao.UserDao;
public class UserServiceImpl implements UserService {
    UserDao userDao;
    public UserDao getUserDao() { 
        return userDao; 
    }
    public void setUserDao(UserDao userDao) { 
        this.userDao = userDao; 
    }
    public void findAll() {
        userDao.findAll();
    }
}
  • 在resources中配置ApplicationContext.xml(官方文档1.2.1模板,参考1.2.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl"/>

    <bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
        <property name="userDao" ref="UserDaoImpl"/>
    </bean>

</beans>
  • 用户层:使用我们配置的IOC容器
import com.admin.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserService userService =(UserService) context.getBean("UserServiceImpl");
        userService.findAll();
    }
}

bean的格式:

  • id:唯一标识符。
  • class:指向目标的全限定类名。
  • property:赋值。其中name表示的是赋值对象id,ref表示引用类型,value表示基本数据类型(int,string)。

1.4 IOC初体验

IOC,全称:Inversion of Control,控制反转。

1.4.1 什么是控制反转?

就是说,我们对程序的控制,不再是程序主动控制了,而是人为控制程序的动作。
比如,一开始都是程序需要什么,我们就new一个对象给程序。
现在是,我们给程序了什么,程序用什么。

注意一下我上面那段话的意思。
前者意思是,人需要按照程序来走,就像从A地到B地,我们只先修了一个铁路,就需要一个火车,只有公路,就需要一个汽车。需要什么是写程序时决定的,我们所作的事情就是获得这个需要的对象。
而后者的意思就是,从A地到B地,我们想通过汽车,就可以走公路,我们想坐火车,就走铁路就好了。就是说,路是准备好的,我们只需要自己随意定义工具就好了。

以上是关于控制反转的理解。(个人总结,如有错误欢迎指正)

1.4.2 什么是IOC容器?

官网给了一个图来解释:在这里插入图片描述

大概意思就是pojo和控制元数据都是被Spring容器统一掌握,然后分配使用。

从我们上面的入门样例可以看出来,我们把所有东西都交给了Spring容器来管理。

这个bean就是一个容器,将所有对象交给bean来管理。

需要什么就通过bean来获得。不需要再new东西了。

可以把bean理解成一个map,通过key来获得value。

我们所作的就是修改配置文件就好了。配置文件说白了就是一个记事本,记录着配置,完全可以不用操作源码了。

比如:我们在bean中配置了UserDaoImpl和UserServiceImpl,
那么我们在test中,直接new出来这个bean容器。用到了UserService,直接从bean中传入目标key,然后就获得了UserServiceImpl对象。
同样道理,在UserService中需要用到了UserDao,我们在bean中配置了目标UserDao,那么我们就直接会通过无参构造来获得。

期间我们不用管bean是如何创建对象的,这已经和我们没有关系了,我们只需要修改对应的bean即可。

这就是关于IOC容器的理解。

2.IOC容器

2.1 Bean总览

Spring IOC容器管理一个或多个bean。这些bean是通过我们对bean的配置创建的(<bean/>)
这些容器中默认包含一下元数据:

  • 全限定类名:该bean的实现类
  • bean的配置元素,用于声明bean在容器中的行为(作用域,生命周期,等)
  • 引用其他bean(依赖)。
  • 新建对象中设置其他配置(管理连接池bean的池大小,链接数等限制)。

这个表可以让我们很明确的知道各个属性都干了什么:

Property解释
Class全限定类名
Namebean的唯一标识符
Scope作用域
Constructor arguments构造函数参数
Properties属性
Autowiring mode自动连接模式
Lazy initialization mode延迟初始化模式
Initialization method初始化方法(回调)
Destruction method销毁方法(回调)

2.2 IOC容器创建对象的方式:DI

依赖注入(DI),全称Dependency Injection。它是一个过程,通过该过程,对象只能通过构造函数参数工厂方法参数创建对象后的设置等方式来定义其依赖关系。

2.2.1 依赖解析

在IOC容器执行bean依赖解析:

  • 使用ApplicationContext.xml来配置和初始化所有bean的元数据(通过xml,注解,Java代码等方式)。
  • 对于每个bean,都要在创建bean时将这些依赖项提供给bean
  • 每个属性(构造参数)都要设置其值,或是对IOC容器中另一个bean的引用。

创建容器时的活动:

  • spring容器会验证每个bean的配置,但不会立刻创建bean。
  • 先创建具有单例作用域并设置为预先实例化的bean。
  • 创建bean会创建bean的依赖。(循环依赖会报错)。

2.2.1 基于构造函数的依赖注入。

  • 无参构造创建对象。上面我们用的就是无参构造创建对象,也是默认的创建方法。

  • 有参构造创建对象。

    • 通过下标赋值

      <bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
          <constructor-arg index="0" ref="UserDaoImpl"/>
      </bean>
      

      index表示下标索引,ref表示引用对象。

    • 通过类型赋值

      <bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
          <constructor-arg type="com.admin.dao.UserDao" 	ref="UserDaoImpl"/>
      </bean>
      

      type:全限定类名,参数类型是什么,就传什么。ref:引用对象

    • 通过参数名赋值

      <bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
          <constructor-arg name="userDao" ref="UserDaoImpl"/>
      </bean>
      

      name:对象名,ref:引用对象。

2.2.2 基于set的依赖注入。

set的DI是通过在调用无参(有参)构造bean之后,通过调用bean上的setter方法来实现的。

public class ExampleBean {
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

对于上面那个类,其bean的set配置法:通过set方法注入。

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

2.2.3 依赖注入的详细配置。

对于9种类型的注入:
bean | ref | idref | list | set | map | props | value | null

  • 配置默认bean

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="masterkaoli"/>
    </bean>
    
  • 使用ref:我们上面一直在用,引用类型

  • 使用idref

    <bean id="theTargetBean" class="..."/>
    
    <!--下面两个的效果相同-->
    <bean id="theClientBean" class="...">
        <property name="targetName">
            <idref bean="theTargetBean"/>
        </property>
    </bean>
    <bean id="client" class="...">
        <property name="targetName" value="theTargetBean"/>
    </bean>
    
  • 使用list

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource" />
            </list>
        </property>
    </bean>
    
  • 使用set:

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource" />
            </set>
        </property>
    </bean>
    
  • 使用map:entry种设置key和value

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry key="an entry" value="just some string"/>
                <entry key ="a ref" value-ref="myDataSource"/>
            </map>
        </property>
    </bean>
    
  • 使用property:配置文件中key和value

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setAdminEmails(java.util.Properties) call -->
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.org</prop>
                <prop key="support">support@example.org</prop>
                <prop key="development">development@example.org</prop>
            </props>
        </property>
    </bean>
    
  • 使用value:上面的任意样例都使用了value

  • 使用null:直接一个null标签就可以了

    <bean class="ExampleBean">
        <property name="email">
            <null/>
        </property>
    </bean>
    

2.2.4 扩展依赖注入配置:c,p命名空间

首先导入对应的命名空间:

<beans xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	...>

之后就可以使用了:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--普通的和使用命名空间的比较-->
    
<!--使用c-namespace比较-->
    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
    
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>
    <bean id="beanOne" class="x.y.ThingOne" 
		c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" 
		c:email="something@somewhere.com"/>
    
<!--使用p-namespace比较-->
    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
    
    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>
    <bean name="john-modern" class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>
</beans>

2.3 bean的作用域

官方给了一个完整的表格:

ScopeDescription
singleton(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototypeScopes a single bean definition to any number of object instances.
requestScopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
sessionScopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
applicationScopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocketScopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

后四个是和web有关的,在MVC处涉及。

前两个

  • singleton:单例,就是一个bean被多个bean引用,这些对象是相同的。默认是单例状态在这里插入图片描述
  • prototype:原型,就是一个bean被多个bean引用,分别是不同的对象。在这里插入图片描述

可以写一下xml和测试类测试一下:

pojo:

public class Address {
    private String address;
}
public class Student {
    private String name;
}

bean:

<bean id="Address" class="com.admin.pojo.Address" scope="prototype"/>
<bean id="Student" class="com.admin.pojo.Student" scope="singleton">
    <property name="name" value="admin"/>
</bean>

test:

import com.admin.pojo.Address;
import com.admin.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("Student",Student.class);
        Student student1 = context.getBean("Student",Student.class);
        Address address1 = context.getBean("Address",Address.class);
        Address address2 = context.getBean("Address",Address.class);
        System.out.println(student==student1);
        System.out.println(address1==address2);
    }
}

可以看出他们的异同。

2.4 bean的自动装配

2.4.1 基于xml的自动装配

我们可以通过配置autowire属性来减少配置文件的书写。

bean的自动装配主要分为byName和byType。

  • byName:通过名字自动配置。名字必须相同,大小写也要相同。
  • byType:通过类型自动装配。类型必须保证唯一。
<bean id="Address" class="com.admin.pojo.Address">
    <property name="address" value="123"/>
</bean>

<bean id="Student" class="com.admin.pojo.Student" autowire="byType">
    <property name="name" value="admin"/>
</bean>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("Student",Student.class);
        System.out.println(student);//输出:Student{name='admin', address=Address{address='123'}}
    }
}

2.4.2 基于注解的自动装配

jdk1.5支持注解,spring2.5支持注解。

配置注解环境:导入约束,配置注解的支持。

<?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:annotation-config/>

</beans>

之后直接可以在属性上使用@Autowired来进行自动装配。多个同类型时,可以使用Qualifier指定装配id(需要同类型)

public class Student {
    private String name;
    @Autowired
    @Qualifier(value = "Address2")
    private Address address;
}
<bean id="Address" class="com.admin.pojo.Address">
    <property name="address" value="123"/>
</bean>
<bean id="Address2" class="com.admin.pojo.Address">
    <property name="address" value="123321"/>
</bean>

<bean id="Student" class="com.admin.pojo.Student">
    <property name="name" value="admin"/>
</bean>

在测试类中输出测试:

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("Student",Student.class);
        System.out.println(student);//Student{name='admin', address=Address{address='123321'}}
    }
}

还有一种直接一个注解Resource就可以的。在javax.annotation.Resource中。
jdk中没有的,取maven仓库导一下javax.annotation就可以了。

public class Student {
    private String name;
//    @Autowired
//    @Qualifier(value = "Address2")
    @Resource(name = "Address2")
    private Address address;
}

3. Spring配置

3.1 别名

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

在调用中,userNew和user都会返回user对象,就是起一个其他称呼。

3.2 bean配置

<bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl" name="dao dao1,dao2;dao3"/>

id和class之前我们题到了,这里多了一个name,也是别名的意思,还可以同时取多个别名,中间可以用",“或者” “或者”;"等符号分割。

3.3 import

团队开发一个项目,分工不同的人写不同的类,有不同的bean我们需要将其汇总到一个总的bean中,然后只获得总的bean就好了。
官方文档:1.2.1中提到了如果多个xml,我们如何将其整到一块。

  • A:bean1.xml
  • B:bean2.xml
  • C:bean3.xml

在总的bean中就可以了:

<import resource="bean1.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>

4. 使用注解实现bean

在官方文档1.9和1.10处将的非常详细,我就不抄了,把我认为的终点总结一下吧。
官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/core.html

可以现在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"
       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.admin.pojo"/>
    <context:annotation-config/>

</beans>

之后就可以自由使用注解来进行操作了。

4.1 自动装配的注解@Autowired和@Required

在xml中配置扫描的包,就可以直接使用注解来实现自动装配了。
上面已经介绍过了,下面把例子再抄一份。

  • @Autowired:spring的自动配置bean,配合@Qualifier和@Primary(主要自动添装)使用
    该注解可以在属性,构造函数,set上使用。

    public class Student {
        private String name;
        @Autowired
        @Qualifier(value = "Address2")
        private Address address;
    }
    
  • @Resource:java自带的自动配置。

public class Student {
    private String name;
    
    @Resource(name = "Address2")
    private Address address;
}

4.2 属性的注入@Component

在类和属性上加一些注解就可以实现注解的注入。

  • @Component:将一个类装配到spring容器中,效果等同配置中的:

    <bean id = "..." class = "..." />
    

    在MVC三层架构中,不同层使用的注解有不同的标识,作用都是一样的(将某个类装配到spring容器中)目的只是为了区分不同的层。

    • @Respository:用在dao层的注解
    • @Service:用在Service层的注解
    • @Controller:用在controller层的注解
  • @Value(String key):效果等同

    <property name = "name" value = "value"/>
    

使用样例:

@Component
public class User {
    @Value("aaa")
    public String name;
}

4.2 作用域的配置@Scope

可以直接使用@Scope注解进行作用域的配置
我们上面说的单例和原型。是可以通过这个注解来配置的。@Scope(String);
官方给了一个原型的作用域。

@Scope("prototype")
@Repository//表示是一个dao层的注解效果等同@Component
public class MovieFinderImpl implements MovieFinder {
    // ...
}

4.3 使用配置类代替xml的注解

也可以完全抛弃xml,直接使用注解配置。

4.3.1 创建配置类@Configuration和@Bean

在一个主配置类中配置@Configuration和@Bean相当于再xml中配置了spring容器。

  • @Configuration:表示这是一个配置类,就和我们之前看的beas.xml一样。这个类也会被spring容器托管,本来就是@Component。
  • @Bean:注册一个bean,相当于我们写的bean标签。
import com.admin.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {

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

4.3.2 配置扫描的包@ComponentScan

如果要扫描包,可以在配置类中使用@ComponentScan来描述扫描的包。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

效果等同:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

4.3.3 导入其他配置类@Import

创建另一个配置类MyConfig2.java

@Configuration
public class MyConfig2 {
	//...
}

在之前的主配置类中使用@Import来调用

@Configuration
@ComponentScan("com.admin.pojo")
@Import(MyConfig2.class)
public class MyConfig {

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

4.4 测试类中获取容器

由于我们没有通过xml配置spring容器,而是注解,因此我们就不能再用ClassPathXmlApplicationContext类获取容器了,而是与之相应的注解获取容器AnnotationConfigApplicationContext。

public class MyTest {

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

5. AOP

5.1 使用Spring实现AOP

实现spring的AOP操作有很多种,首先先准备一些不变的东西
首先需要导入包

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

创建一个简单的接口和其实现类.写完之后就不用管它了,就像我们之前学过的动态代理一样,我们不需要对原有的代码做一丝修改.

package com.admin.service;

public interface UserService {
    void add();
    void delete();
    void update();
    void select();
}

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void select() {
        System.out.println("查找了一个用户");
    }
}

通过测试类来查看是否完成想要的操作

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = context.getBean("UserService",UserService.class);
        service.add();
    }
}

5.1.1 方法一:通过springAPI来实现动态代理

写一些日志实现:用来做切入操作.这里需要实现spring的代理接口.

package com.admin.log;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(method.getClass().getName()+"类调用了"+method.getName()+"接口");
    }
}
public class AfterLog implements AfterReturningAdvice {
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println(method.getClass().getName()+"调用了"+method.getName()+"方法");
    }
}

配置applicationConftext.xml来注册到spring中

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

    <context:component-scan base-package="com.admin"/>
    <context:annotation-config/>

    <bean id="UserService" class="com.admin.service.UserServiceImpl"/>
    <bean id="Log" class="com.admin.log.Log"/>
    <bean id="AfterLog" class="com.admin.log.AfterLog"/>

    <!--这里就是配置spring的AOP的核心配置-->
    <aop:config>
        <!--配置切入点,注意expression表达式 类型 包.类.方法名(参数)-->
        <aop:pointcut id="PointCut" expression="execution(* com.admin.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>

测试类种可以测试成功

java.lang.reflect.Method类调用了add接口
增加了一个用户
java.lang.reflect.Method调用了add方法

这种方法的好处就是能够更加随意地操作我们的类.只需要实现对应的接口就好了.
我们可以在类中放入更多的东西.

5.1.2 方法二:通过spring自定义切面来实现动态代理

新加入一个自定义切面类,方法名什么都随便写,我这是方便自己些的before和after.

package com.admin.aspect;

public class MyAspect {
    public void before(){
        System.out.println("这是一个前置通知");
    }
    public void after(){
        System.out.println("这是一个后置通知");
    }
}

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

    <context:component-scan base-package="com.admin"/>
    <context:annotation-config/>

    <bean id="UserService" class="com.admin.service.UserServiceImpl"/>
    <bean id="Log" class="com.admin.log.Log"/>
    <bean id="AfterLog" class="com.admin.log.AfterLog"/>
    <bean id="MyAspect" class="com.admin.aspect.MyAspect"/>

    <aop:config>
        <!--自定义切面,ref表示要引用的类-->
        <aop:aspect ref="MyAspect">
            <aop:pointcut id="pointcut" expression="execution(* com.admin.service.UserService.*(..))"/>
            <!--配置方法出现的位置-->
            <aop:after method="after" pointcut-ref="pointcut"/>
            <aop:before method="before" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试输出:

这是一个前置通知
增加了一个用户
这是一个后置通知

这种方法的好处就是简单,将所有的东西都放入一个普通类中了
而且一个切面下去,东西全在里面.
不过缺点就是由于是普通类,所以可能支持的操作就变少了.

5.1.3 方法三:通过注解来实现动态代理

新建一个代理类,其中标上注解

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 AnnoPointCut {

    @Before("execution(* com.admin.service.UserService.*(..))")
    public void before(){
        System.out.println("前置通知");
    }
    @After("execution(* com.admin.service.UserService.*(..))")
    public void after(){
        System.out.println("后置通知");
    }

    @Around("execution(* com.admin.service.UserService.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        Signature signature = jp.getSignature();
        System.out.println("signatuer:"+signature);

        Object proceed = jp.proceed();
        System.out.println("环绕后");
    }
}

xml中需要添加支持:

输出结果:

环绕前
signatuer:void com.admin.service.UserService.add()
前置通知
增加了一个用户
环绕后
后置通知

其实注解操作只需要几个简单的点就好了:

  • @Aspect:定义该类为切面类.
  • @Before:定义该方法是前置通知
  • @After:定义该方法为后置通知.
  • @Around:定义该方法为环绕通知.
    • 其中根据ProceedingJoinPoint.proceed()来确定环绕前后.
    • 需要抛出异常.

还有其他方法都是一样的.

注意一下环绕通知被前置和后置包括在里面.

和直接使用JDK来实现动态代理相比,其实用spring的AOP的话还是很简单的.

6.整合Mybatis

首先需要导包:

  • Mybatis的所有包:MySql,mybatis
  • 导入spring的所有包:spring-jdbc,spring-webmvc,spring-test,aspectjweaver
  • 导入mybatis和spring整合包:mybatis-spring
  • 导入单元测试和lombok方便:junit,lombok

6.1 Mybatis-Spring简介

Mybatis-Spring有官方文档的:http://mybatis.org/spring/zh/index.html

6.1.1 什么是Mybatis-Spring?

简介中简要的介绍了什么是Mybatis-Spring:

  • MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中.
  • 它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器mapperSqlSession 并注入到 bean
  • 可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring

6.1.2 版本情况

Mybatis-Spring有两个版本,分别是2.0和1.3,注意一下使用的区别.
在这里插入图片描述

6.2 回顾Mybatis

导完包后,我们简单的写一个Mybatis的crud操作,然后通过这个简单的样例来修改整合到我们的Mybatis-Spring中.

  1. 首先确定接口和实体类

    package com.admin.dao;
    
    import com.admin.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
    //    void add();
    //    void delete();
    //    void update();
        List<User> selectAll();
    }
    

    实体类:用来limbok来简化代码

    package com.admin.pojo;
    
    import lombok.Data;
    
    @Data
    public class User {
        private String name;
    }
    
  2. 然后是配置Mybatis-Config.xmlmapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="db.properties"/>
    
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.admin.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/admin/dao/UserMapper.xml"/>
    </mappers>
</configuration>

其中我用了db.properties资源文件来引入数据库的变量

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username=root
password=123456

根据上面的接口,我们在resoueces中创建目录结构相同的mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.admin.dao.UserMapper">

    <select id="selectAll" resultType="user">
        select * from user;
    </select>

</mapper>
  1. 写一个测试类来测试是否成功连接

    先写一个工具类来简化获取SqlSession代码

    package com.admin.utils;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class MybatisUtils {
    
        private static SqlSessionFactory factory;
        static {
            String resource = "mybatis-config.xml";
            try {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                factory = new SqlSessionFactoryBuilder().build(inputStream);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static SqlSession getSqlSession(){
            return factory.openSession();
        }
    }
    

    然后测试类中调用测试即可.

    package com.admin;
    
    import com.admin.dao.UserMapper;
    import com.admin.pojo.User;
    import com.admin.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class MyTest {
        @Test
        public void test1(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.selectAll();
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    }
    

    输出结果:由于我们配置的有简单的日志,因此可以看到获取数据的过程.

    Checking to see if class com.admin.pojo.User matches criteria [is assignable to Object]
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    PooledDataSource forcefully closed/removed all connections.
    Opening JDBC Connection
    Created connection 1906879951.
    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
    ==>  Preparing: select * from user; 
    ==> Parameters: 
    <==    Columns: id, name
    <==        Row: 1, a
    <==        Row: 2, b
    <==        Row: 3, c
    <==      Total: 3
    User(name=a)
    User(name=b)
    User(name=c)
    Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf]
    Returned connection 1906879951 to pool.
    

    Nice~下面我们就参考官方的入门来改造一下这个Mybatis.

6.3 快速上手

按官方的来.

  1. 首先要再Spring中配置一个SqlSessionFactory和至少一个数据映射器

    我们可以直接使用SqlSessionFactoryBean来创建SqlSessionFactory不过需要先配置好数据源dataSource.

    顺便创建出我们的sqlSession.

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 绑定mybatis配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 配置映射器mapper 当然也可以在mybatis-config中配置 -->
        <property name="mapperLocations" value="classpath:com/admin/dao/UserMapper.xml"/>
    </bean>
    
    <!--SqlSessionTemplate 就是我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    

    此时,我们已经不需要工具类了,因为我们已经可以获得SqlSession了.就可以删掉获得SqlSession的工具类了.

  2. 创建UserMapperImpl实现类

    由于Spring中所有东西都在容器中被托管.为了我们能够直接调用接口中的方法,我们需要一个实现类来实现它

    直接调用sqlSession中的getMapper方法来通过MybatisMapper.xml来实现具体的功能,我们要做的就是把功能绑定起来罢了.

    package com.admin.dao.Impl;
    
    import com.admin.dao.UserMapper;
    import com.admin.pojo.User;
    import org.mybatis.spring.SqlSessionTemplate;
    
    import java.util.List;
    
    public class UserMapperImpl implements UserMapper {
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        public List<User> selectAll() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectAll();
        }
    }
    

    将这个Impl实现类注册到bean中

    <bean id="userMapper" class="com.admin.dao.Impl.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
  3. 测试类中通过Spring来调用方法

    package com.admin;
    
    import com.admin.dao.UserMapper;
    import com.admin.pojo.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        @Test
        public void test1(){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
            for (User user : userMapper.selectAll()) {
                System.out.println(user);
            }
        }
    }
    

    输出结果:

    JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7642df8f] will not be managed by Spring
    ==>  Preparing: select * from user; 
    ==> Parameters: 
    <==    Columns: id, name
    <==        Row: 1, a
    <==        Row: 2, b
    <==        Row: 3, c
    <==      Total: 3
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@791d1f8b]
    User(name=a)
    User(name=b)
    User(name=c)
    

可以看出来,改造还是挺成功的,我们进行了以下的修改

  • 新加入一个applicationContext.xml的Spring配置类
  • mybatis-config.xml中的数据源,mappers删掉了.
  • 获取SqlSession的工具类删掉了.
  • 新增了一个接口的实现类

6.4 快速小结

但是我们调用的时候就不再需要sqlSession这些东西了,spring都在后面帮我们做好了,我们显式的使用方法只需要通过IoC容器就好了.

大家可能此处有些小疑问:为什么要多写一个impl呢,我们在测试类中一样可以用sqlsession来获取mapper来使用方法啊.
(其实这就是我当时想到的一个小问题,不过这东西稍微想一下就清楚了)

我们来看一下如果不写impl会表现出什么效果:
我们也不需要多改什么,只是不用impl罢了,修改以下测试类,使用sqlSession来获取方法.

public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        SqlSessionTemplate sqlSession = context.getBean("sqlSession",SqlSessionTemplate.class);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        for (User user : userMapper.selectAll()) {
            System.out.println(user);
        }
    }
}

和之前的比较以下,是不是多了很多东西,我们在测试类中还要调用dao层的东西.这非常不利于我们的开发.

如果将他们分开,dao专注dao的事情,测试类只用测试就好了,其他层各司其职.可以更加简化.而且impl中也没有多写什么,只是把测试类中的代码放到了impl里面,我们以后测试类中就不用重复写了.这也方便了很多.

总之,尽量减少耦合.

6.5 通过继承SqlSessionDaoSupport来实现impl

使用SqlSession这一个标签中提到了另一种使用方法:http://mybatis.org/spring/zh/sqlsession.html

直接在impl中继承一个SqlSessionDaoSupport,然后就可以直接通过getSqlSession()方法获得SqlSessionTemplate了.然后就是正常的方法了.
这个类中不需要显式的注入SqlSession.

package com.admin.dao.Impl;

import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectAll() {
        return getSqlSession().getMapper(UserMapper.class).selectAll();
    }
}

同样写完之后需要到Ioc中去配置对应的bean.虽然这个类中没有构造和set方法,但是继承的SqlSessionDaoSupport中有一个set方法.在这里插入图片描述

通过这个set方法我们可以知道,传入FactoryTemplate都是可以的.

<bean id="userMapper2" class="com.admin.dao.Impl.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

在测试类中测试效果相同

package com.admin;

import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class MyTest {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
        for (User user : userMapper.selectAll()) {
            System.out.println(user);
        }
    }
}
/*
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@68be8808] will not be managed by Spring
==>  Preparing: select * from user; 
==> Parameters: 
<==    Columns: id, name
<==        Row: 1, a
<==        Row: 2, b
<==        Row: 3, c
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3336e6b6]
User(name=a)
User(name=b)
User(name=c)
*/

7.声明式事务

7.1 回顾事务

事务是什么:

  • 把一组业务当成一个业务来做,要么都成功要么都失败
  • 事务在项目中非常重要,涉及到数据的一致性问题!非常重要!
  • 确保完整性和一致性.

事务的ACID原则

  • 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态.
  • **一致性(Consistency)😗*事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定.
  • 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离.
  • **持久性(Durability)😗*当事务正确完成后,它对于数据的改变是永久性的.无论发生什么都不会再改变了

7.2 Spring中的事务管理

7.2.1 声明式事务

spring给我们已经整理好了所有的操作.我们只需要再xml中配置就行了.

使用Spring的事务管理,直接调用.我们不需要编写事务操作.

7.2.1.1 方法一
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource" />
</bean>
<!--结合AOP实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给那些方法配置事务配置事务的-->
    <tx:attributes>
        <tx:method name="add"/>
        <tx:method name="delete"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!--配置事务的切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.admin.dao.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

修改以下方法测试一下:

package com.admin.dao.Impl;

import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectAll() {
        UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
        userMapper.deleteUserById(4);
        userMapper.addUser(new User(5,"e"));

        return getSqlSession().getMapper(UserMapper.class).selectAll();
    }

    public void addUser(User user) {
        getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    public void deleteUserById(int id) {
        getSqlSession().getMapper(UserMapper.class).deleteUserById(id);
    }
}

修改一下再看看.发现已经被事务管理了.

7.2.1.2 方法二:

而官方的操作是另一种方法,通过直接配置在方法中.

  1. 创建DataSourceTransactionManager对象.

  2. 使用Spring的事务名命空间或者JtaTransactionManagerFactoryBean.

    使用一个子类或者由容器指定一个子类作为事务管理器

  3. 在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个 SqlSession。如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。

    注意,如果你想使用由容器管理的事务,而不想使用 Spring 的事务管理,你就不能配置任何的 Spring 事务管理器。并必须配置 SqlSessionFactoryBean 以使用基本的 MyBatis 的 ManagedTransactionFactory

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>

<tx:jta-transaction-manager />

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
  </property>  
</bean>

7.2.2 编程式事务

用的似乎不多,多少抄一下把.留个印象.

MyBatis 的 SqlSession 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession 或映射器。也就是说,Spring 总是为你处理了事务。

你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit()SqlSession.rollback()SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的映射器时,这些方法也不会暴露出来。

无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。

如果你想编程式地控制事务,请参考 the Spring reference document(Data Access -Programmatic transaction management-)。下面的代码展示了如何使用 PlatformTransactionManager 手工管理事务。

TransactionStatus txStatus =
    transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
  userMapper.insertUser(user);
} catch (Exception e) {
  transactionManager.rollback(txStatus);
  throw e;
}
transactionManager.commit(txStatus);

在使用 TransactionTemplate 的时候,可以省略对 commitrollback 方法的调用。

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
  userMapper.insertUser(user);
  return null;
});

注意:虽然这段代码使用的是一个映射器,但换成 SqlSession 也是可以工作的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值