SSM学习笔记1-Spring

Spring学习笔记

Spring简单入门

一、概念

spring是分层的java SE/EE应用full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。

二、优势

  1. 提供IoC容器,解耦;
  2. AOP编程的支持
  3. 声明式事务的支持
  4. 方便测试
  5. 方便集成各种框架
  6. 降低javaEE API的使用难度(封装)
  7. java源码可供学习

三、开发步骤

  1. 导入Spring开发的基本坐标
  2. 编写Dao接口和实现类
  3. 创建Spring核心配置文件
  4. 在Spring配置文件中配置UserDaoImpl
  5. 使用Spring的API获得Bean实例(getBean参数为配置的id)

各步骤示例如下:

Ioc入门案例

1.导入Spring坐标

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

2.定义Spring管理的类(接口)

package com.itheima.service;

public interface BookService {
    public void save();
}
public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();
    @Override
    public void save() {
        bookDao.save();
    }
}

3创建Spring配置文件,配置对应类作为Spring管理的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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.导入spring的坐标spring-context,对应版本是5.2.10-->

<!--    2.配置bean-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

</beans>

bean定义时id属性不能重复
4.初始化Ioc容器,通过容器获取bean

public class App2 {
    public static void main(String[] args) {
        //3.获取Ioc容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4.获取bean
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();

    }
}
DI入门案例

1.删除使用new的方式创建对象的代码

2.提供依赖对象对应的setter方法

public class BookServiceImpl implements BookService {

    //5.删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
    //6.提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

3.配置service与dao之间的关系

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.导入spring的坐标spring-context,对应版本是5.2.10-->

<!--    2.配置bean-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--        7.配置server与dao的关系-->
<!--        property标签表示配置当前bean的属性-->
<!--        name属性表示配置哪一个具体的属性(相当于new一个叫name的东西)-->
<!--        ref属性表示参照哪一个bean-->
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

bean相关

bean基础配置

请添加图片描述

在这里插入图片描述

在这里插入图片描述

bean实例化

  • bean本质上就是对象,创建bean使用构造方法完成
一、 构造方法(常用)

提供可访问的构造方法

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDao.....");
    }
}

配置

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

无参构造方法如果不存在,将抛出异常BeanCreationException

二、 静态工厂(了解)

静态工厂

public class OrderDaoFactory{
	public static OrderDao getOrderDao(){
	return new OrderDaoImpl();
	}
}

配置

<bean
	id="orderDao"
    factory-method="getOrderDao"
    class="com.itheima.factory.OrderDaoFactory"
    />
三、 实例工厂(了解)

实例工厂

public class UserDaoFactory{
	public UserDao getUserDao(){
	return new UserDaoImpl();
	}
}

配置

<bean id="userDaoFactory" class="com.itheima.factory.UserDaoFactory"/>

<bean
	id="userDao"
    factory-method="getUserDao"
    factory-bean="userDaoFactory"
    />
四、使用FactoryBean实例化(实用)

FactoryBean(实用):
(1)FactoryBean
implements FactoryBean
实现getObject()和getObjectType(还有isSingleton()可以修改是否单例)
(2)配置
class = “com. itheima.factory.UserDaoFactoryBean”

bean生命周期

bean生命周期:bean从创建到销毁的整体过程

生命周期控制:

方法一:

提供生命周期控制方法

public class BookDaoImpl implements BookDao{
	public void save(){
        
    }
    public void init(){
        
    }
    public void destroy(){
        
    }
}

配置生命周期s控制方法

<bean id="bookDao"
      class="com.itheima.dao.impl.BookDaoImpl"
      
      init-method="init"
      destroy-method="destroy"
      />

方法二:实现 InitializingBean,DisposableBean接口(了解)

public class BookServiceImpl implements BookService , InittializingBean, DisposableBean {
	public void save(){
        
    }
    public void afterPropertiesSet() throws Exception(){
        
    }
    public void destroy throws Exception(){
        
    }
}
生命周期:

1.初始化容器
(1)创建对象,内存分配
(2)执行构造方法
(3)执行属性注入——setBookDao()
(4)执行bean初始化方法——afterPropertiesSet()
2.使用bean
执行业务操作
3.销毁容器
执行bean销毁方法——destroy()

依赖注入方式

1.setter注入

​ (1)简单类型,如int

在这里插入图片描述

(2)引用类型,如String

在这里插入图片描述

2.构造器注入

​ (1)简单类型

在这里插入图片描述

​ (2)引用类型

在这里插入图片描述

在这里插入图片描述

依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现

  2. 可选依赖使用setter注入进行,灵活性强

  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注

  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入

  6. 自己开发的模块推荐使用setter注入

自动装配

1.定义:IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中

2.方式

  • 按类型(常用)

  • 按名称

  • 按构造方法

  • 不启用自动装配

3.实现

(1)service的bean里去掉ref=“dao”

(2)service保留setter方法

​ 自动配置也需要入口获取资源再注入
(3)service的bean里配置

​ 方式一:按类型——autowire=“byType”
​ 注1:dao的bean不能去
​ 注2:可用的dao的bean必须唯一
​ 方式二:按名称——autowire=“byName”
​ 注:可以解决不唯一的问题(按setter方法名去找依赖的bean id)

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

4.特征

自动装配用于引用类型依赖注入,不能对简单类型进行操作
使用按类型装配时( byType )必须保障容器中相同类型的bean唯一,推荐使用
使用按名称装配时( byName )必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用自动装配优先级低于setter注入与构造器

集合注入

数组

List

Set

Map

Properties

1.数组

bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <property name="array">
        <array>
            <value>100</value>
            <value>200</value>
            <value>300</value>
        </array>
    </property>
</bean>

2.list

    <property name="list">
        <list>
            <value>itheima</value>
            <value>itcast/value>
            <value>boxuegu</value>
        </list>
    </property>

3.Set

    <property name="set">
        <set>
            <value>itheima</value>
            <value>itcast/value>
            <value>boxuegu</value>
        </set>
    </property>

4.Map

    <property name=map>
        <map>
            <entry key="country" value="china"/>
            <entry key="province" value="henan"/>
            <entry key="city" value="kaifeng"/>
        </map>
    </property>

5.Properties

    <property name=properties>
        <props>
            <prop key="country">china</prop>
            <prop key="province">henan</prop>
            <prop key="city">kaifeng</prop>
        </props>
    </property>

案例:数据源对象管理(Spring管理第三方资源)

一、Druid连接对象

1.pom.xml

​ 1.引入依赖Druid
​ 2.刷新maven

2.applicationContext.xml

管理DruidDataSource对象

<!--   管理DruidDataSource对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

二、C3p0连接对象

1.pom.xml

​ 1.搜索maven repository
​ 2.引入依赖C3p0、mysql
​ 2.刷新maven

2.applicationContext.xml

管理DataSource对象

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
</bean>

Spring加载properties文件

在这里插入图片描述

<?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
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-beans.xsd
            ">
<!--    1.开启context命名空间-->
<!--    2.使用context空间加载properties文件-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    3.使用属性占位符${}读取properties文件中的属性-->
    <bean class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
问题:
	系统环境变量有个userame(优先级更高)
	如果properties里写 username=666
	那么不会正常注入,而是会注入系统环境变量设置的值
解决:关闭系统属性
	<context:property-placeholder 
		location="jdbc.properties" 
		system-properties-mode="NEVER"/>
问题:多个配置文件——jdbc.properties、jdbc2.properties
解决1:逗号分隔
	<context:property-placeholder 
		location="jdbc.properties, jdbc2.properties"
		system-properties-mode="NEVER" />
解决2:通配符(最规范)
	<context:property-placeholder 
		location="classpath:*.properties"
		system-properties-mode="NEVER" />
仅读取当前工程目录:location="classpath:*.properties"
可读取依赖的jar包:location="classpath*:*.properties"

容器

创建容器

1.加载类路径下的配置文件(常用)
AppLicationContext ctx = new ClassPathXmLApplicationContext(“applicationContext.xmL”);
2.从文件系统下加载
AppLicationContext ctx = new FileSystemXmApplicationContext(“D:\workspace\spring\…(绝对路径)”)

获取bean

1.使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean(“bookDao”);
2.使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean(“bookDao”, BookDao.class);
3.使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);

容器类层次结构

Ctrl+F12 查看类中各方法

Ctrl+H 查看接口继承

BeanFactory

是所有容器类的顶层接口

虽然BeanFactory也可以作为容器
但是ApplicationContext提供的功能要比BeanFactory多

1.核心区别:加载bean的时机不一样:
    ApplicationContext	立即加载bean
    BeanFactory			延迟加载bean

2.现象:在只加载配置文件后运行,看是否执行构造方法

3.ApplicationContext实现延迟加载
	<bean id="bookDao" class="com.itheima. dao.imp1.BookDaoImpl" lazy-init="true"/>

核心容器总结

bean相关

在这里插入图片描述

依赖注入相关

在这里插入图片描述

Spring注解开发

注解开发定义bean

使用@Component定义bean

@Component("bookDao")
public class BookDaoImpl implements BookDao {

}

核心配置文件中通过组件扫描加载bean

<context:component-scan base-package="com.itheima"/>
注解说明
@Component使用在类上用于实例化Bean
@Controller使用在web层类上用于实例化Bean
@Service使用在service层类上用于实例化Bean
@Repository使用在dao层类上用于实例化Bean

纯注解开发

Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道

Java类代替Spring核心配置文件

<context:component-scan base-package="com.itheima"/>

改为

@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}

@Configuration注解用于设定当前类为配置类

@ComponentScan(“com.itheima”)注解用汉语设定扫描路径,此注解只能添加一次,多个数据用数组格式

@ComponentScan({"com.itheima.service","com.itheima.dao"})

读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

注解开发作用范围与生命周期管理

使用@Scope标注Bean的范围:

@Repository
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}

作用范围与生命周期管理

注解说明
@Scope标注Bean的作用范围
@PostConstruct使用在方法上标注该方法是Bean的初始化方法
@PreDestroy使用在方法上标注该方法是Bean的销毁方法

注解开发依赖注入

注解说明
@Autowired使用在字段上用于根据类型依赖注入
@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入
@Resource相当于@Autowired+@Qualifier,按照名称进行注入
@Value注入普通属性

使用@Autowired注解开启自动装配模式(按类型)

注意:无需提供setter方法

使用@Qualifier注解开启指定名称装配bean

注意:@Qualifier注解无法单独使用,必须配合@Autowired

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
}

使用@Value注解注入简单类型

@Value("${jdbc.driver}")

使用@PropertySource注解加载properties文件(不允许用通配符*)

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
注解说明
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。作用和在 Spring 的 xml 配置文件中的<context:component-scan base-package=“com.itheima”/>一样
@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource用于加载.properties 文件中的配置

为第三方bean配置资源

核心配置类

@Configuration		//标志该类是Spring的核心配置类

@ComponentScan("com.itheima")   //代替组件扫描:<context:component-scan base-package="com.itheima"/>
//<import resource=""/>
@Import({DataSourceConfiguration.class})		//把子配置类引入主配置类 代替了引入其他文件:<import>
public class SpringCofiguration {

}

子配置类

@PropertySource("classpath:jdbc.properties")	//加载jdbc配置文件
public class DataSourceConfiguration {

    @Value("${jdbc.driver}")	// 用SpEl避免写死了而导致耦合
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")  //Spring会将当前方法的返回值以指定名称(dataSource)存储到Spring容器中
    public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }

}

注解开发总结

在这里插入图片描述

Spring整合Mybatis

分析

在这里插入图片描述

在这里插入图片描述

整合mybatis

在这里插入图片描述

在这里插入图片描述

Spring整合Juit

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

解决上述问题的思路:

  • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它

  • 将需要进行测试Bean直接在测试类中进行注入

Spring集成Junit步骤

① 导入spring集成Junit的坐标

<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上--> 
<dependency> 
    <groupId>org.springframework</groupId> 
    <artifactId>spring-test</artifactId> 
    <version>5.0.2.RELEASE</version>
</dependency> 
<dependency> 
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version> 
    <scope>test</scope>
</dependency>

② 使用@Runwith注解替换原来的运行期

@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}

③ 使用@ContextConfiguration指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}

④ 使用@Autowired注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})

public class SpringJunitTest {
	@Autowired
	private UserService userService;
}

⑤ 创建测试方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
	@Autowired
	private UserService userService;
	@Test
	public void testUserService(){
		userService.save();
	} 
}

Spring集成Junit步骤

① 导入spring集成Junit的坐标

② 使用@Runwith注解替换原来的运行期

③ 使用@ContextConfiguration指定配置文件或配置类

④ 使用@Autowired注入需要测试的对象

⑤ 创建测试方法进行测试

AOP

AOP基本介绍

AOP(Aspect Oriented Programming:面向切面编程),一种编程范式,指导开发者如何组织程序结构

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

  • 优势:减少重复代码,提高开发效率,并且便于维护

  • Spring理念:无侵入式

在这里插入图片描述

AOP核心概念

  • 连接点:程序执行过程中的任意位置
    • 在SpringAOP中,理解为方法的执行
  • 切入点:匹配连接点的式子
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:
      • 匹配多个方法:
  • 通知:在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面:描述通知与切入点的对应关系

AOP快速入门(基于注解)

①导入 AOP 相关坐标

②定义dao接口与实现类

③定义通知类

④定义切入点

⑤绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

⑥定义通知类受Spring容器管理,并定义当前类为切面类

⑦开启Spring对AOP注解驱动支持

具体如下:

①导入 AOP 相关坐标

<!--导入spring的context坐标,context依赖aop-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

②定义dao接口与实现类

public interface BookDao {
    public void save();
    public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save...");
    }

    public void update() {
        System.out.println("book dao update...");
    }
}

③定义通知类

public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

④定义切入点

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

}

⑤绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

⑥定义通知类受Spring容器管理,并定义当前类为切面类

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

⑦开启Spring对AOP注解驱动支持

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

AOP工作流程

在这里插入图片描述

SpringAOP本质:代理模式

AOP切入点表达式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

AOP通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知,它们分别是:

  • 前置通知 (@Before) 。

    在切入点前

  • 后置通知 (@After)。

    在切入点后

  • 环绕通知 (@Around) :(优先级最高)

    在切入点首和尾

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        pjp.proceed();
        System.out.println("around after advice");
        return ret
    }
  • 返回通知 (@AfterReturning) 。(了解即可)

    正常执行完毕后(没有抛异常)的时候执行

  • 异常通知 (@AfterThrowing) 。(了解即可)

    当前通知方法在切入点方法运行抛出异常后执行

案例:测量业务层接口万次执行效率

在这里插入图片描述

AOP通知获取数据

在这里插入图片描述

在这里插入图片描述

Spring事务

Spring事务简介

相关概念介绍
  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败

数据层有事务我们可以理解,为什么业务层也需要处理事务呢?

举个简单的例子,

  • 转账业务会有两次数据层的调用,一次是加钱一次是减钱
  • 把事务放在数据层,加钱和减钱就有两个事务
  • 没办法保证加钱和减钱同时成功或者同时失败
  • 这个时候就需要将事务放在业务层进行处理。
转账案例-需求分析

接下来通过一个案例来学习下Spring是如何来管理事务的。

先来分析下需求:

需求: 实现任意两个账户间转账操作

需求微缩: A账户减钱,B账户加钱

为了实现上述的业务需求,我们可以按照下面步骤来实现下:
①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)

②:业务层提供转账操作(transfer),调用减钱与加钱的操作

③:提供2个账号和操作金额执行转账操作

④:基于Spring整合MyBatis环境搭建上述操作

转账案例-环境搭建
步骤1:准备数据库表

之前我们在整合Mybatis的时候已经创建了这个表,可以直接使用

create database spring_db character set utf8;
use spring_db;
create table tbl_account(
    id int primary key auto_increment,
    name varchar(35),
    money double
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);
步骤2:创建项目导入jar包

项目的pom.xml添加相关依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

  </dependencies>
步骤3:根据表创建模型类
public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;
	//setter...getter...toString...方法略    
}
步骤4:创建Dao接口
public interface AccountDao {

    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
步骤5:创建Service接口和实现类
public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
    }

}
步骤6:添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
步骤7:创建JdbcConfig配置类
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
步骤8:创建MybatisConfig配置类
public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
步骤9:创建SpringConfig配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

步骤10:编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);
    }

}
事务管理

上述环境,运行单元测试类,会执行转账操作,Tom的账户会减少100,Jerry的账户会加100。

这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }

}

这个时候就模拟了转账过程中出现异常的情况,正确的操作应该是转账出问题了,Tom应该还是900,Jerry应该还是1100,但是真正运行后会发现,并没有像我们想象的那样,Tom账户为800而Jerry还是1100,100块钱凭空消息了,银行乐疯了。如果把转账换个顺序,银行就该哭了。

不管哪种情况,都是不允许出现的,对刚才的结果我们做一个分析:

①:程序正常执行时,账户金额A减B加,没有问题

②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

当程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上,而Spring的事务管理就是用来解决这类问题的。

Spring事务管理具体的实现步骤为:

步骤1:在需要被事务管理的方法上添加注解@Transactional
public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
	@Transactional
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }

}

注意:

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上
步骤2:在JdbcConfig类中配置事务管理器
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

**注意:**事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManager

步骤3:开启事务注解@EnableTransactionManagement

在SpringConfig的配置类中开启

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

步骤4:运行测试类

会发现在转换的业务出现错误后,事务就可以控制回顾,保证数据的正确性。

知识点1:@EnableTransactionManagement
名称@EnableTransactionManagement
类型配置类注解
位置配置类定义上方
作用设置当前Spring环境中开启注解式事务支持
知识点2:@Transactional
名称@Transactional
类型接口注解 类注解 方法注解
位置业务层接口上方 业务层实现类上方 业务方法上方
作用为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)

Spring事务角色

这节中我们重点要理解两个概念,分别是事务管理员事务协调员

通过上面例子的分析,我们就可以得到如下概念:

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
事务配置

在这里插入图片描述

上面这些属性都可以在@Transactional注解的参数上进行设置。

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。

  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。

  • rollbackFor:当出现指定异常进行事务回滚

  • noRollbackFor:当出现指定异常不进行事务回滚

    • 思考:出现异常事务会自动回滚,这个是我们之前就已经知道的

    • noRollbackFor是设定对于指定的异常不回滚,这个好理解

    • rollbackFor是指定回滚异常,对于异常事务不应该都回滚么,为什么还要指定?

      • 这块需要更正一个知识点,并不是所有的异常都会回滚事务,比如下面的代码就不会回滚

        public interface AccountService {
            /**
             * 转账操作
             * @param out 传出方
             * @param in 转入方
             * @param money 金额
             */
            //配置当前接口方法具有事务
            public void transfer(String out,String in ,Double money) throws IOException;
        }
        
        @Service
        public class AccountServiceImpl implements AccountService {
        
            @Autowired
            private AccountDao accountDao;
        	@Transactional
            public void transfer(String out,String in ,Double money) throws IOException{
                accountDao.outMoney(out,money);
                //int i = 1/0; //这个异常事务会回滚
                if(true){
                    throw new IOException(); //这个异常事务就不会回滚
                }
                accountDao.inMoney(in,money);
            }
        
        }
        
  • 出现这个问题的原因是,Spring的事务只会对Error异常RuntimeException异常及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

    • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚

      @Service
      public class AccountServiceImpl implements AccountService {
      
          @Autowired
          private AccountDao accountDao;
      	 @Transactional(rollbackFor = {IOException.class})
          public void transfer(String out,String in ,Double money) throws IOException{
              accountDao.outMoney(out,money);
              //int i = 1/0; //这个异常事务会回滚
              if(true){
                  throw new IOException(); //这个异常事务就不会回滚
              }
              accountDao.inMoney(in,money);
          }
      
      }
      
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串

  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串

  • isolation设置事务的隔离级别

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
要实现案例不管是否转账成功都添加日志

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

具体如何解决,就需要用到之前我们没有说的propagation属性

修改logService改变事务的传播行为

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

运行后,就能实现我们想要的结果,不管转账是否成功,都会记录日志。

事务传播行为可选参数

            throw new IOException(); //这个异常事务就不会回滚
          }
          accountDao.inMoney(in,money);
      }
  
  }
  ```
  • 出现这个问题的原因是,Spring的事务只会对Error异常RuntimeException异常及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

    • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚

      @Service
      public class AccountServiceImpl implements AccountService {
      
          @Autowired
          private AccountDao accountDao;
      	 @Transactional(rollbackFor = {IOException.class})
          public void transfer(String out,String in ,Double money) throws IOException{
              accountDao.outMoney(out,money);
              //int i = 1/0; //这个异常事务会回滚
              if(true){
                  throw new IOException(); //这个异常事务就不会回滚
              }
              accountDao.inMoney(in,money);
          }
      
      }
      
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串

  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串

  • isolation设置事务的隔离级别

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
要实现案例不管是否转账成功都添加日志

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

具体如何解决,就需要用到之前我们没有说的propagation属性

修改logService改变事务的传播行为

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

运行后,就能实现我们想要的结果,不管转账是否成功,都会记录日志。

事务传播行为可选参数

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海蛋2333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值