目录

Spring 概述

Spring 模块

核心容器 

AOP和Instrumentation 

消息 

数据访问/集成 

Web 

测试 

核心依赖包 

Spring IOC 和 DI

IOC

DI

IOC 和 DI 的关系

IOC 容器

Spring Bean 装配机制

XML 装配 bean

 使用 XML 文件来配置 bean 的信息

构造注入

setter 赋值注入 

级联注入

单例与多例

工厂方法创建 Bean

包扫描管理 bean

SPEL 与资源调用

对象属性拷贝

单元测试

spring 与 mybatis 整合

Mybatis 开起数据库字段名驼峰命名规则自动转换

方式一:

 方式二:

事务的传播行为


Spring 概述

Spring 是一个开源框架 。Spring 为简化企业级开发而生,使用 Spring,JavaBean 就可以实现很多以前要靠 EJB 才能实现的功能。同样的功能,在 EJB 中要通过繁琐的配置和复杂的代码才能够实现, 而在 Spring 中却非常的优雅和简洁。

Spring 是一个 IOC(DI)和 AOP 容器框架。

Spring 的特性

(1)非侵入式:基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API

(2) 依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。

(3) 面向切面编程:Aspect Oriented Programming——AOP

(4) 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期

(5) 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中 可以使用 XML 和 Java 注解组合这些对象。

Spring 模块

Spring框架的功能被有组织的分散到约20个模块中。分成几个部分

Spring 后端吊起前端 后端开发spring_java-ee

核心容器 

  • spring-core和spring-beans模块:提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。
  • spring-context上下文模块:建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。
  • spring-context-support模块:支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。
  • spring-expression模块:提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。它还支持列表投影和选择以及常见的列表聚合。

AOP和Instrumentation 

  • spring-aop模块:提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,还可以以类似于.NET属性的方式将行为信息合并到代码中。
  • spring-aspects模块:单独提供了与AspectJ的集成。
  • spring-instrument模块:提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

消息 

  • spring-messaging模块:(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

数据访问/集成 

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

  • spring-jdbc模块:提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。
  • spring-tx模块:支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。
  • spring-orm模块:为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。
  • spring-oxm模块:提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。
  • spring-jms模块:(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

Web 

  • Web层由spring-web,spring-webmvc和spring-websocket 模块组成。
  • spring-web模块:提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。
  • spring-webmvc模块:(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

测试 

  • spring-test模块:支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

核心依赖包 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.star</groupId>
    <artifactId>SpringFramework</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <spring.framework>5.3.10</spring.framework>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.framework}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.framework}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.framework}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.framework}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.framework}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jcl</artifactId>
            <version>${spring.framework}</version>
        </dependency>
    </dependencies>

</project>

也可以只导入 spring-context 会自动导入其他相关依赖

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.10</version>
 </dependency>

Spring IOC 和 DI

IOC

ioc (Inversion of Control) 反转控制

在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式, 增加了学习成本,同时降低了开发效率。

反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发 的效率。这种行为也称为查找的被动形式。

DI

DI (Dependency Injection) 依赖注入

DI 是 IOC 的另一种展现方式:即组件以一些预先定义好的方式(例如:parameter、construct、setter 方法)接受来自于容器的资源注入。

IOC 和 DI 的关系

IOC 控制反转是一种思想

DI 依赖注入是一种设计模式

使用 DI 依赖注入实现 IoC 控制反转

如:

在没有 IoC 之前,我们要在 A 类中使用 B 类,就要在 A 类中 new 出 B 类的实例,这样 A 类和 B 类之间就出现了耦合。

public class A {
	private B b = new B();
}

使用了IoC之后,我们就把实例化这样操作交给框架去帮我们做了

public class A {
	@autowire
    private B b; // 无需创建,由 bean 工厂注入到 b 变量中
}

IOC 容器

Spring 容器是创建程序中 bean 的核心,通过 DI 来协调这些对象之间的关系。

Spring 自带多个容器的实现,可以归纳为两种不同的类型:

  1. bean 工厂(BeanFactory),最简单的容器,提供基本的 DI 支持。
  2. 应用上下文(ApplicationContext),继承了(BeanFactory),并提供应用框架级别的服务。

BeanFactory管理任何类型对象的高级配置机制。 ApplicationContext

BeanFactory提供了配置框架和基本功能,并ApplicationContext添加了更多企业特定的功能。

使用方法

// 通过 BeanFactory 读取配置文件
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);// 新增 Xml阅读器
reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));// 规则注册入容

// 根据id值获取bean实例对象
User user = (User) factory.getBean("user");
// 通过类类型获取bean实例对象
User user2 = factory.getBean(User.class);
// 根据id值 和 类类型获取bean实例对象
User user3 = factory.getBean("user", User.class);
System.out.println(user);

 或者Spring 自带了多种类型的应用上下文:

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
  • ClassPathXmlApplicationContext:从类路径(包含JAR文件)下的一个或多个XML配置文件中加载上下文。
  • FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文。
  • XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文。
public static void main( String[] args )
    {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:ApplicationContext.xml");
        Object meeting = applicationContext.getBean("meeting");
        System.out.println(meeting);
    }

 无论是从文件系统中装载应用上下文还是从类路径下装载应用上下文,将bean加载到bean工厂的过程都是相似的。 

Spring Bean 装配机制

作为开发人员,需要告诉 Spring 哪些对象要作为 bean 装配到容器中,bean 和 bean之间的依赖关系。

Spring提供了三种主要的装配机制:

  1. 在 XML 中进行显示配置
  2. 在 Java 中进行显示配置
  3. 隐式的 bean 发现机制和自动装配

XML 装配 bean

ApplicationContext 接口基于XML方式的主要实现类

  • ClassPathXmlApplicationContext:对应类路径下的 XML 格式的配置文件
  • FileSystemXmlApplicationContext:对应文件系统中的 XML 格式的配置文件
// 方式1:在项目中类路径中 resources 下查找xml文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 方式2:在指定的文件系统路径下查找xml文件
ApplicationContext context1 = new FileSystemXmlApplicationContext("D:/applicationContext.xml");

// 根据id值获取bean实例对象
User user = (User) context.getBean("user");
// 通过类类型获取bean实例对象
User user2 = context.getBean(User.class);
// 根据id值 和 类类型获取bean实例对象
User user3 = context.getBean("user", User.class);
System.out.println(user);

 使用 XML 文件来配置 bean 的信息

在项目中任意位置创建 xml 文件(建议在 resource 目录下),建 applicationContext.xml (文件名任意)

构造注入

在实际开发中有时需要被管理的对象没有提供无参构造方法,因此我们需要在创建对象时直接传入对应的值。spring 使用 constructor-arg 设置对象的构造方法的参数,通常可以使用 name 指定构造方法的参数名或通过 index 指定构造方法参数的索引。比如: 与下面设置 id 的方式等效。

<bean id="user2" class="cn.hx.User">
    <constructor-arg name="name" value="李四"/>
    <constructor-arg name="id" value="2"/>
</bean>

注意:在 xml 中包含特殊字符,必须使用 包裹,且不能在写在 constructor-arg 节点的属性上。

<constructor-arg name="name" >
    <value><![CDATA[<李四]]></value>
</constructor-arg>

setter 赋值注入 

通过 setter 方法给 bean 的属性赋值

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--
        使用bean元素定义一个由IOC容器创建的对象
        class 属性指定用于创建bean的全类名
        id 属性指定用于引用bean实例的标识,唯一
     -->
    <bean id="user" class="com.star.spring.bean.User">
        <!-- 
			使用property子元素为bean的属性赋值
			如果不赋值则为变量的默认值
 		-->
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="age" value="20"/>
    </bean>
</beans>

默认使用 bean 的 setter 方法赋值

Spring 后端吊起前端 后端开发spring_后端_02

  

级联注入

在 Spring 中可以通过一组内置的 xml 标签(例如:, 或 )来配置集合属性。配置 java.util.List 类型的属性,需要指定 标签,在标签里包含一些元素。这些标签可以通过 指定简单的常量值,通过 指定对其他 Bean 的引用。通过 指定内置 Bean 定义。通过 指定空元素。甚至可以内嵌其他集合。数组的定义和 List 一样,都使用 配置 java.util.Set 需要使用 标签,定义元素的方法与 List 一样。

在用户实体中加入 Car 集合

public class Car {
    private String company;
    private Integer maxSpeed;
    private Float price;
}

级联时使用 list 表明该处级联类型为 list 集合,里面使用 ref 应用其他 bean 对象,级联 set 与 map 也与此类似。

<bean id="user1" class="cn.hx.User">
    <property name="id" value="1"/>
    <property name="name" value="张三"/>
    <property name="cars">
        <list>
            <ref bean="car1"/>
        </list>
    </property>
</bean>   

<bean id="car1" class="cn.hx.Car">
    <property name="company" value="大众"/>
    <property name="maxSpeed" value="280"/>
    <property name="price" value="30"/>
</bean>

 注意:设置为空使用下面的语法

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

单例与多例

为了节省内存,spring 中的对象默认使用单例模式。即无论从容器中获取多少次对象,得到的都是同一个。但在实际开发中有时会用到多例,即希望每次从容器中获取对象都是新的对象。spring 提供 scop 属性来指定对象获取是单例还是多例。多例的 bean 又叫原型的。

prototype:原型的。每次调用 getBean 方法都会返回一个新的 bean。且在第一次调用 getBean 方法时才创建实例。

singleton:单例的。每次调用 getBean 方法都会返回同一个 bean。且在 IOC 容器初始化时即创建 bean 的实例。默认值

<bean id="user" class="cn.hx.User" scope="prototype">
    <property name="id" value="1"/>
    <property name="name" value="张三"></property>
</bean>

应用场景:

1.数据库连接 Connection 对象不能设计成单例,否则会出现多个线程使用同一个连接完成数据库的不同操作,也许上一个线程还没有查询完数据就会被下一个线程拿去修改数据库记录,非常容易出现错误。

2. service 层的对象就不需要设计成原型的,因为 service 层没有过多的参数,不容易导致线程安全问题,创建过多的对象反而耗用大量内存意义不大。

工厂方法创建 Bean

在项目开发中,有时需要使用工厂方法创建 bean,spring 支持常用的工厂方法如静态工厂方法创建和实例工厂方法创建 bean。

1.静态工厂方法创建 bean

public class CarFactory {
    static Map<String, Car> cars = new HashMap<>();

    static {
        cars.put("car1", new Car("宝马", 120, 122222.0));
        cars.put("car2", new Car("奥迪", 120, 122222.0));
    }

    public static Car getCar(String name) {
        return cars.get(name);
    }
}

 获取 bean

<bean id="car1" class="cn.hx.CarFactory" factory-method="getCar">
    <constructor-arg value="car2"/>
</bean>

2.实例工厂方法获取 bean

public class CarFactory {
    Map<String, Car> cars = new HashMap<>();

    public CarFactory() {
        cars.put("car1", new Car("宝马", 120, 122222.0));
        cars.put("car2", new Car("奥迪", 120, 122222.0));
    }

    public Car getCar(String name) {
        return cars.get(name);
    }
}

获取bean

<bean id="carFactory" class="cn.hx.CarFactory"></bean>
<bean id="car1" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="car1"></constructor-arg>
</bean>

包扫描管理 bean

使用 xml 方式配置 bean 的依赖关系复杂且麻烦,为了解决对象在容器注入麻烦的问题,spring 推出了包扫描与声明类注解配合使用的方式,对于开发人员编写的类只需要在包扫描范围内,使用指定的声明类注解即可将对象加入 spring 容器中。

sping 使用 context:component-scan 将指定包下面的带有声明 bean 的注解的类加入到 spring 容器管理。

<context:component-scan base-package="cn.hx"/>

常用的声明类注解有如下几个,它们的功能和作用在 spring 中完全一模一样,都是将自己交给 spring 管理。唯一的区别就是它们所用的业务环节不同。

  • @Service 用于标注业务层组件
  • @Controller 用于标注控制层组件
  • @Repository 用于标注数据访问组件,即 DAO 组件
  • @Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

注入类注解:在 spring 容器管理的 bean 对象需要依赖其他 bean 对象时,就可以在对应对象属性或其 set 方法上使用注入类注解完成依赖注入。spring 容器中常用 @Autowired 和 @Resource 两个注解完成依赖注入。@Autowired 属于 Spring 的注解,@Resource 不属于 Spring 的注解,是 JDK1.6 支持的注解。它们的区别是:

@Autowired 默认按类型装配, 依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false @Autowired(required = false) 也可以使用名称装配,配合 @Qualifier 注解

public class TestServiceImpl {
     @Autowired
     @Qualifier("userDao")
     private UserDao userDao;
}

@Resource 默认按名称进行装配,通过 name 属性进行指定,name 为属性的名字时可以省略。

总结:将除了实体以外的其他对象加入 spring 容器管理,并通过彼此的依赖自动注入。使程序开发更加简便灵活,在内存耗用,对象管理方面更加优秀。

SPEL 与资源调用

在实际开发中,经常在 spring 的 xml 使用配置文件中的属性,spring 加载 properties 文件中一般使用 context:property-placeholder,加载完成后使用 ${} 获取配置文件中对应属性,花括号内是对应属性的键。通常配置文件在 resource 目录下。如 db.properties 文件,使用 context:property-placeholder 加载 properties 文件,${name} 获取配置文件中 name 的属性值。

<context:property-placeholder location="classpath:db.properties" file-encoding="utf-8"/>
<bean id="car1">
    <property name="company" value="${name}"/>
    <property name="maxSpeed" value="280"/>
    <property name="price" value="30"></property>
</bean>

如果该属性需要的 spring 包扫描的类中使用可以使用 @Value("${name}") 获取配置文件内的属性。

在 spring 中加载配置文件也可以使用注解的方式 @PropertySource("classpath:db.properties") 它与 context:property-placeholder 是等效的。

spring 不仅仅可以获取配置文件里面的内容它还可以做一些简单的运算,如下案例:可以注入其他对象的属性、普通字符串、操作系统的信息、随机数、某个文件的内容等。如下代码在输出 Resource 时用到 commons-io 工具类。

依赖如下:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

案例:

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
@PropertySource("classpath:db.properties")
public class PropertyTest {

    @Value("${id}")
    private int id;
    //注入配置文件的属性

    @Value("I love you")
    private String normal;
    //字符串原样注入

    @Value("#{systemProperties['os.name']}")
    private String osName;
    //注入操作系统信息

    @Value("#{pet.name}")
    private String otherName;
    //注入容器内其它 bean 的属性

    @Value("#{T (java.lang.Math).random()*100}")
    private Integer random;
    //注入 100 以下的随机数

    @Value("classpath:1.txt")
    private Resource resource;
    //注入classpath下文本文件里面内容

    @Value("http://58.42.239.163:8888/")
    private Resource resourceUrl;
    //注入网站地址的响应内容

    public String toString1() throws Exception {
        return "[normal=" + normal + ",id="+id+", osName=" + osName + ", otherName=" + 
    otherName
                + ", random=" + random + ", resource=" + 
    IOUtils.toString(resource.getInputStream())
                + ", resourceUrl=" + IOUtils.toString(resourceUrl.getInputStream(), "UTF- 
    8") + "]";
    }
}

注意:

1.在 spring 通过注解 @Value 中获取 properties 文件里面的值使用 $ 前缀。

2.获取系统信息、容器类其他对象的信息、方法调用后的结果等使用 # 前缀。

3.获取文件或网页内容不使用前缀,且使用 Resource 接收。

对象属性拷贝

BeanUtils 在 spring 工具库中是比较常用的对象属性拷贝工具,BeanUtils.copyProperties(源头,目标);一般使用如下:

User user1 = .....;
User user2 = .....;
BeanUtils.copyProperties(user1,user2);

当在完成两个对象拷贝是需要负略某些属性可以使用对应的重载方法 BeanUtils.copyProperties(源头,目标,"忽略的属性");  但在实际开发中拷贝非空属性是比较常用的。创建方法将对象的非空属性列出后传入 BeanUtils 即可拷贝非空属性。

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;

public class BeanUtil {

    private static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null)
                emptyNames.add(pd.getName());
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }


    public static void copyPropertiesIgnoreNull(Object source, Object target) {
        BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
    }

}

单元测试

在实际开发中有时需要对 spring 容器中的大量的类和方法进行测试,使用 main 方法显然不能很好的满足需求,单元测试更好的解决测试方面的注入和运行问题。使用 spring 的单元测试需要导入相应的依赖:spring-test,同时要求 junit 不小于 4.12 版本。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.12.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

引入依赖后在测试源码目录下创建如下类并添加相应的注解使其能

import cn.hx.spring.view.PetView;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring-context.xml")
public class AppTest {
    @Autowired
    private PetView petView;

    @Test
    public void sendSimpleEmail() throws InterruptedException {
        petView.show();
    }
}

spring 与 mybatis 整合

spring 能够与常见框架整合,如 mybatis,hibernate,redis 等。这使得大部分框架中的对象都可以从 spring 中获取到。而且对象被 spring 管理后可以很方便的使用 spring 的依赖注入和面向切面。

使用 spring 与 mybatis 整合需要如下几步:

1.添加依赖:由于数据库连接相关对象都交给 spring 管理,所以依赖中多出了 spring-jdbc 依赖。同时 mybatis 为了和 spring 整合开发了 mybatis-spring 整合包。

<!--spring -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.12.RELEASE</version>
</dependency>

<!--mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2.配置数据库连接信息,文件名通常为 db.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mydb
jdbc.username=root
jdbc.password=123456

3.spring 核心配置文件,由于数据库连接等对象都被 spring 管理,所以配置时不再需要 mybatis 的核心配置文件,如果在开发中确实需要用到 mybatis 的核心配置文件也可以配置 sqlsessionfactory 的 configlocation。

<?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 
        https://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="cn.hx"/>
    <context:property-placeholder location="classpath:db.properties"/>
    <!--    配置数据库数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--    创建 session 工厂-->
    <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <!--告诉spring mybatis接口的位置-->
    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.hx.mapper"/>
    </bean>
    <!--    配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--    使用注解的方式完成事务控制-->
    <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

</beans>

注意:

1.mapperScanner 配置主要配置 mybatis 的接口位置。

2.注解式事务是通过 @Transactional 注解即可完成该方法所有数据库操作要么一起成功,要么一起失败的业务逻辑。使用事务时数据库引擎必须使用 innoDB。

@Transactional
public  void update(){
    User user1 = new User();
    user1.setId(2);
    user1.setName("O");
    userMapper.update(user1);
    System.out.println(1 / 0);
    User user2 = new User();
    user2.setId(3);
    user2.setName("Y");
    userMapper.update(user2);
}

3.spring5 版本不再支持老版本的 log4j 版本,为了显示日志。通常我们使用 logback。导入如下依赖。

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

配置文件 logback.xml 放在 classpath 下。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p --- [%t] %-40.40logger{39} : %m%n</pattern>
        </encoder>
    </appender>
    <!--根logger-->
    <root level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
    </root>
</configuration>

Mybatis 开起数据库字段名驼峰命名规则自动转换

mapUnderscoreToCamelCase:是否启用下划线与驼峰式命名规则的映射(如first_name => firstName)

<setting name="mapUnderscoreToCamelCase" value="true"/>

方式一:

applicationContext.xml中引入mybatis配置 

<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 数据库连接池 -->
    <property name="dataSource" ref="dataSource"/>
    <!-- 加载Mybatis全局配置文件 -->
    <property name="configLocation" value="/WEB-INF/classes/mybatis/SqlMapConfig.xml"/>
</bean>

SqlMapConfig.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>
    <!--配置全局属性-->
    <settings>
        <!--开启驼峰命名转换-->
       <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

 方式二:

在applicationContext-dao.xml中直接配置

<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="factory">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.glc.domain"/>
    
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration">
            <property name="mapUnderscoreToCamelCase" value="true"/>
        </bean>
    </property>

</bean>

事务的传播行为

Spring 中,有 7 种类型的事务传播行为。事务传播行为是 Spring 框架提供的一种事务管理方式,它不是数据库提供的。不知道大家是否听说过“不要在 service 事务方法中嵌套事务方法,这样会提交多个事务”的说法,其实这是不准确的。了解了事务传播行为之后,相信你就会明白!

spring 对事务的控制,是使用 aop 切面实现的,我们不用关心事务的开始,提交 ,回滚,只需要在方法上加 @Transactional 注解,这时候就有问题了。

  • 场景一:serviceA 方法调用了 serviceB 方法,但两个方法都有事务,这个时候如果 serviceB 方法异常,是让 serviceB 方法提交,还是两个一起回滚。
  • 场景二:serviceA 方法调用了 serviceB 方法,但是只有 serviceA 方法加了事务,是否把 serviceB 也加入 serviceA 的事务,如果 serviceB 异常,是否回滚 serviceA 。
  • 场景三:serviceA 方法调用了 serviceB 方法,两者都有事务,serviceB 已经正常执行完,但 serviceA 异常,是否需要回滚 serviceB 的数据。

因为 spring 是使用 aop 来代理事务控制 ,是针对于接口或类的切面完成,所以在同一个 service 类中两个方法的调用,传播机制是不生效的。

事务行为

说明

PROPAGATION_REQUIRED

支持当前事务,假设当前没有事务。就新建一个事务

PROPAGATION_SUPPORTS

支持当前事务,假设当前没有事务,就以非事务方式运行

PROPAGATION_MANDATORY

支持当前事务,假设当前没有事务,就抛出异常

PROPAGATION_REQUIRES_NEW

新建事务,假设当前存在事务。把当前事务挂起

PROPAGATION_NOT_SUPPORTED

以非事务方式运行操作。假设当前存在事务,就把当前事务挂起

PROPAGATION_NEVER

以非事务方式运行,假设当前存在事务,则抛出异常

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

 如:PROPAGATION_REQUIRED 表示如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。可以把事务想像成一个胶囊,在这个场景下方法 B 用的是方法 A 产生的胶囊(事务)。

案例:

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}