0505-Spring

第一章 Spring

1.1 Spring简介

  1. Spring框架的核心

    • 是抽取出来的高度可重用的代码, 多个可重用模块的集合, 形成:JavaEE领域的整体解决方案
    • Spring框架是是一个IOC和AOP的容器框架
    • Spring容器包含并且管理应用中的对象的关系以及生命周期
  2. Spring技术栈

    Spring技术 功能说明
    spring farmwork Spring核心
    spring data Spring数据支持
    spring security Spring安全认证
    spring boot Spring场景启动自动配置
    spring cloud Spring微服务解决方案
  3. Spring优点

    • 为JavaEE开发提供了一站式的解决方案 :从基础的IOC容器,已经衍生为Cloud Native的基础设施
    • 非侵入 : 用Spring开发的应用不依赖Spring的API
    • 依赖注入 : 是对IOC思想的实现
    • 面向切面编程 : 是对面向对象的扩展与增强
    • 轻量级 : 可以把直接在Tomcat等符合Servlet规范的web服务器上的Java应用称为轻量级的应用
    • 模块化 : 添加特定模块可以解决特定场景的功能

1.2 Spring模块划分

NiVHvd.png
spring 测试模块 测试组件说明
spring-test 测试组件
spring 核心 核心模块说明
spring-beans Bean工厂与装配
spring-core 核心模块 依赖注入IOC和DI的最基本实现
spring-context 上下文,即IOC容器
spring-context-support 对IOC的扩展,以及IOC子容器
spring-context-indexer 类管理组件和Classpath扫描
spring-expression 表达式语句
spring AOP 切面编程说明
spring-aop 面向切面编程,CGLB,JDKProxy
spring-aspects 集成AspectJ,Aop应用框架
spring-instrument 动态Class Loading模块
spring Data 说明
spring-jdbc 提供JDBC主要实现模块,用于简化JDBC操作
spring-orm 主要集成Hibernate,jpa,jdo等
spring-tx spring-jdbc事务管理
spring-oxm 将java对象映射成xml数据或将xml映射为java对象
spring-jms 发送和接受消息
spring web 说明
spring-web 最基础的web支持,主要建立在核心容器上
spring-webmvc 实现了spring mvc的web应用
spring-websocket 主要与前端页的全双工通讯协议
spring-webflux 一个新的非阻塞函数式Reactive Web框架
spring message 说明
spring-messaging 主要集成基础报文传送应用
spring Instrumentation 说明
spring-instrument

1.3 Spring技术点

概述 技术点
Java语言特性 反射、动态代理、枚举、泛型、注解、ARM、Lambda语法
设计模式与设计思想 OOP、IoC、AOP、DDD、TDD、GOF23
JavaAPI的分装与简化 JDBC、Servlet、JPA、JMX、Bean、Validation
第三方框架的整合 Mybatis、Hibernate、Redis、SpringMVC

第二章 Spring IOC

第三章 Spring AOP

3.1 AOP概述

        AOP(Aspect Oriented Programming)是一种编程思想,其核心是在不破坏原有结构的基础上对需要增强的功能进行增强;AOP通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。是在OOP的基础上对OOP做的增强和扩展,并且可以做到对业务逻辑的隔离,降低代码的耦合;

  • AOP底层原理:代理模式

    • JDK动态代理
    • CGLIB动态代理
  • 所需要依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.6.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.6.RELEASE</version>
    </dependency>

3.2 AOP术语

  • 切点(PointCut):就将要增强目标方法的执行过程中的关键点(方法执行前、方法执行后、抛出异常后等等);
  • 连接点(Joinpoint):切点的定义一个前后的范围,那么连接点就是要指定具体的某个方法的切点,由切点表达式指定;
  • 增强(Advice) :切点由表达式定义好后,需要指定在这个连接点做什么样的增强以及执行是时机,增强的方式定义在一个方法中,在AOP中称为通知,通知方式如下:
    • 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
    • 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
    • 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
    • 返回通知(afterReturning):在执行业务代码后无异常,会执行的操作
    • 环绕通知(around):这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
  • 切面(Aspect):切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
  • 目标对象(Target):需要被加强的业务对象
  • 织入(Weaving):织入就是将增强添加到对目标类具体连接点上的过程。
    织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
  • 代理类(Proxy):一个类被AOP织入增强后,就产生了一个代理类。

3.3 基于注解

1. @Pointcut

  • execution:拦截指定规则的公共方法

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * ① 第一个* 表示方法修饰符,只支持public,可以不写
    * ② 包名中的 .. 表示当前包以及子包
    * ③ *Service 表示以Service结尾的类
    * ④ *(..) 括号前的*表示任意方法
    * ⑤ (..) 括号中的 .. 表示任意参数
    */
    @Pointcut(value = "execution(* com.ms.aop..*Service.*(..))")
  • @annotation:匹配有指定注解的方法

    1
    2
    3
    4
    5
    6
    // 被拦截的方法有 @Annotation1 类型的注解
    @annotation(com.ms.aop.jannotation.demo2.Annotation1)

    // 接口方法有注解
    @Annotation1
    void m1(){}
  • args:匹配方法中的参数

    1
    2
    3
    4
    5
    6
    7
    8
    // 匹配一个参数,且类型为String
    @Pointcut(value = "args(java.lang.String)")

    // 匹配多个参数,且类型为也匹配
    @Pointcut(value = "args(java.lang.String,java.lang.Integer)")

    // 匹配任意多个参数,且第一个参数符合, .. 表示参数通配符
    @Pointcut(value = "args(java.lang.String,..)")
  • @args:方法参数所属的类型上有指定的注解被匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 匹配1个参数
    @Pointcut("@args(com.ms.aop.jargs.demo1.Anno1)")

    // 匹配多个参数,且多个参数所属的类型上都有指定的注解
    @Pointcut(value = "@args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2)")

    //匹配多个参数,且第一个参数所属的类中有Anno1注解
    @Pointcut(value = "@args(com.ms.aop.jargs.demo2.Anno1,..)")


    // 参数类型有注解
    @Anno1
    public class User {
    private String name;
    }
  • this:代理对象为指定的类型会被拦截,在Spring中如果代理对象有接口首先会使用JDK动态代理,否则才会使用CGLIB动态代理;在this表达式中代理对象有接口,代理对象不会被拦截,因为被代理是是this表达式中对应的接口类型;

    1
    2
    // 如果有接口则不会被代理,Spring首先会使用JDK动态代理
    @Pointcut(value = "this(com.ms.aop.jthis.demo1.ServiceImpl)")
  • @target:匹配的目标对象的类有一个指定的注解

    1
    2
    3
    4
    5
    6
    7
    // 目标对象中包含com.ms.aop.jtarget.Annotation1注解,调用该目标对象的任意方法都会被拦截
    @Pointcut(value = "@target(com.ms.aop.jtarget.Annotation1)")

    @Annotation1
    public class Service {

    }
  • target:目标对象为指定的类型被拦截

    1
    2
    // 目标对象为 ServiceImpl 类型的会被代理
    @Pointcut(value = "target(com.ms.aop.target.ServiceImpl)")
  • @within:如果类上有指定类型的注解,则该类任意方法都会被代理

    1. @target(注解A):判断被调用的目标对象中是否声明了注解A,如果有,会被拦截
    2. @within(注解A): 判断被调用的方法所属的类中是否声明了注解A,如果有,会被拦截
    3. @target关注的是被调用的对象,@within关注的是调用的方法所在的类
    1
    2
    // 声明有com.ms.aop.jwithin.Annotation1注解的类中的所有方法都会被拦截
    @Pointcut(value = "@within(com.ms.aop.jwithin.Annotation1)")
  • within:根据表达式代理指定包或者子包,表达式格式:包名.* 或者 包名..*within()和execution()函数不同的是,within()所指定的连接点最小范围只能是类,而execution()所指定的连接点可以大到包,小到方法入参。 所以从某种意义上讲,execution()函数功能涵盖了within()函数的功能

    1
    2
    3
    4
    5
    // 拦截包中任意方法,不包含子包中的方法
    @Pointcut(value = "within(com.ms.aop.within.*)")

    // 拦截包或者子包中定义的方法
    @Pointcut(value = "within(com.ms.aop.within..*)")

2. @Before

标识一个前置增强方法,相当于BeforeAdvice的功能

3. @After

final增强,不管是抛出异常或者正常退出都会执行

4. @AfterReturning

后置增强,似于AfterReturningAdvice, 方法正常退出时执行

5. @AfterThrowing

异常抛出增强,相当于ThrowsAdvice

6. @Around

环绕增强,相当于MethodInterceptor

3.3 基于XML

第四章 Spring TX

4.1 事务概述

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

4.2 Spring 事务管理

1. Spring事务方式

  • 编程式事务管理:是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
  • 声明式事务管理:建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

2. Spring事务核心接口

sGyMzq.png
  • JDBC事务:如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。

    1
    2
    3
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    </bean>
  • Hibernate事务:如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。

    1
    2
    3
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  • Java持久化API事务(JPA):Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。

    1
    2
    3
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    </bean>
  • Java原生API事务:如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager

    1
    2
    3
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

4.3 Spring事务属性

1. 只读

        事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

2. 超时

        为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

3. 回滚规则

        这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

  • rollbackFor:让原本不回滚的异常发生时候事务回滚;
  • noRollbackFor:让原本会回滚的异常发生时候事务不回滚;

4. 隔离规则

  1. 事务并发存在的问题

    • 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
    • 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
    • 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
  2. 事务的隔离级别

    隔离级别 含义
    ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
    ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
    ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
    ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
    ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

5. 传播行为

        事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

传播行为 含义
REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务
REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

4.4 Spring - 编程式事务

1. TransactionTemplate

1
2
3
4
5
6
7
8
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
Object result = tt.execute(
new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
updateOperation();
return resultOfUpdateOperation();
}
}); // 执行execute方法进行事务管理

2. PlatformTransactionManager

1
2
3
4
5
6
7
8
9
10
11
12
//定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
// 数据库操作
dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status);// 回滚
}

4.5 Spring - 声明式事务

1. 基于注解的声明式事务

  • 在配置文件中指定事务管理器,并开启基于注解的transaction

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <?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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.spring5"/>
    <!-- Hikari Datasource -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl"
    value="jdbc:mysql://localhost:3306/case-project?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <!-- 连接只读数据库时配置为true, 保证安全 -->
    <property name="readOnly" value="false"/>
    <!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 -->
    <property name="connectionTimeout" value="30000"/>
    <!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 -->
    <property name="idleTimeout" value="600000"/>
    <!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
    wait_timeout参数(show variables like '%timeout%';) -->
    <property name="maxLifetime" value="1800000"/>
    <!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) -->
    <property name="maximumPoolSize" value="60"/>
    <property name="minimumIdle" value="10"/>
    </bean>

    <!-- 声明式事务管理器 -->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" name="transactionManager">
    <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 开启基于注解是事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>
  • 事务注解标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class xxxService{
    @Transactional(
    readOnly = false,
    timeout = 3,
    rollbackFor = ArrayIndexOutOfBoundsException.class,
    isolation = Isolation.DEFAULT,
    propagation = Propagation.REQUIRES_NEW)
    public void updateBayBook(String user, String book, int store) {
    // TODO
    }
    }

2. 基于AOP配置的声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">

<context:component-scan base-package="com.spring5"/>
<!-- Hikari Datasource -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/case-project?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 连接只读数据库时配置为true, 保证安全 -->
<property name="readOnly" value="false"/>
<!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 -->
<property name="connectionTimeout" value="30000"/>
<!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 -->
<property name="idleTimeout" value="600000"/>
<!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
wait_timeout参数(show variables like '%timeout%';) -->
<property name="maxLifetime" value="1800000"/>
<!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) -->
<property name="maximumPoolSize" value="60"/>
<property name="minimumIdle" value="10"/>
</bean>

<!-- 声明式事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" name="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置AOP切面表达式关联切点 -->
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.spring5.demo03.*Service(..))"/>
<aop:advisor advice-ref="txConfig" pointcut-ref="txPoint"/>
</aop:config>
<!-- 事务切点 -->
<tx:advice id="txConfig" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="update*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>

4.6 Spring事务测试环境

  • 数据表:基本逻辑是用户买书,用户余额减少、书的库存减少、书的销售收入新增;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    drop table if exists tx_book_user;
    drop table if exists tx_book_store;
    drop table if exists tx_book_count;

    create table tx_book_user
    (
    name varchar(20),
    balance int
    ) comment '用户余额表';

    create table tx_book_store
    (
    id int primary key auto_increment,
    name varchar(20),
    store int,
    price int
    ) comment '书的库存表';
    create table tx_book_count
    (
    id int primary key auto_increment,
    name varchar(20),
    count int
    ) comment '书的收入表';

    insert into tx_book_user(name, balance) VALUES ('Tom',20000);
    insert into tx_book_store(id, name, store, price) VALUES (1,'java',100,20);
    insert into tx_book_store(id, name, store, price) VALUES (2,'go',100,30);
    insert into tx_book_store(id, name, store, price) VALUES (3,'css',100,50);

    insert into tx_book_count(id, name, count) VALUES (1,'java',100);
    insert into tx_book_count(id, name, count) VALUES (2,'go',100);
    insert into tx_book_count(id, name, count) VALUES (3,'css',100);
  • 实体类:BookUser用户、BookCount书的收入、BookStore书的库存;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Setter
    @Getter
    @ToString
    public class BookUser {
    private String name;
    private Double balance;
    }

    @Setter
    @Getter
    @ToString
    public class BookCount {
    private Integer id;
    private String name;
    private Double count;
    }

    @Setter
    @Getter
    @ToString
    public class BookStore {
    private Integer id;
    private String name;
    private Integer store;
    private Double price;
    }
  • dao层:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    // 查询当前的余额,并将销售新增到收入
    @Repository
    public class BookCountDao {
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    public BookCountDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }
    public BookCount selectBookCount(String bookName) {
    String sql = "select id,name,count from tx_book_count where name = ?";
    System.out.println("selectBookCount sql =" + sql);
    System.out.println("selectBookCount param = " + bookName);
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(BookCount.class), bookName);
    }
    public int addCount(BookCount count) {
    String sql = "update tx_book_count set count = ? where id = ?";
    System.out.println("addCount sql =" + sql);
    System.out.println("addCount param = " + count.getCount() + "\t" + count.getId());
    return jdbcTemplate.update(sql, count.getCount(), count.getId());
    }
    }

    // 查询当前库存,并在当前库存基础上减少销售数量
    @Repository
    public class BookStoreDao {
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    public BookStoreDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }
    public BookStore selectBookStore(String bookName) {
    String sql = "select id,name,store,price from tx_book_store where name = ?";
    System.out.println("selectBookStore sql =" + sql);
    System.out.println("selectBookStore param = " + bookName);
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(BookStore.class), bookName);
    }
    public int minusStore(BookStore store) {
    String sql = "update tx_book_store set store = ? where id = ?";
    System.out.println("minusStore sql =" + sql);
    System.out.println("minusStore param = " + store.getStore() + "\t" + store.getId());
    return jdbcTemplate.update(sql, store.getStore(), store.getId());
    }
    }
    // 查询用户当前余额,并根据购买的数较少金额
    @Repository
    public class BookUserDao {
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    public BookUserDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }
    public BookUser selectUser(String username) {
    String sql = "select `name`,`balance` from `tx_book_user` where `name` = ?";
    System.out.println("selectUser sql =" + sql);
    System.out.println("selectUser param = " + username);
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(BookUser.class), username);
    }
    public int updateBalance(BookUser bookUser) {
    String sql = "update tx_book_user set balance = ? where name = ?";
    System.out.println("updateBalance sql =" + sql);
    System.out.println("updateBalance param = " + bookUser.getBalance() + "\t" + bookUser.getName());
    return jdbcTemplate.update(sql, bookUser.getBalance(), bookUser.getName());
    }
    }
  • service层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @Service
    public class BookService {
    private final BookUserDao bookUserDao;
    private final BookCountDao bookCountDao;
    private final BookStoreDao bookStoreDao;

    @Autowired
    public BookService(BookUserDao bookUserDao, BookCountDao bookCountDao, BookStoreDao bookStoreDao) {
    this.bookUserDao = bookUserDao;
    this.bookCountDao = bookCountDao;
    this.bookStoreDao = bookStoreDao;
    }

    @Transactional(
    readOnly = false,
    timeout = 3,
    rollbackFor = ArrayIndexOutOfBoundsException.class,
    isolation = Isolation.DEFAULT,
    propagation = Propagation.REQUIRES_NEW)
    public void updateBayBook(String user, String book, int store) {
    BookUser bookUser = bookUserDao.selectUser(user);
    BookStore bookStore = bookStoreDao.selectBookStore(book);
    BookCount bookCount = bookCountDao.selectBookCount(bookStore.getName());

    // 用户买书先减去用户余额
    bookUser.setBalance(bookUser.getBalance() - bookStore.getPrice() * store);
    int updateBalance = bookUserDao.updateBalance(bookUser);

    // 再减去书的库存:买了 store 本
    bookStore.setStore(bookStore.getStore() - store);
    int minusStore = bookStoreDao.minusStore(bookStore);

    // 最后书的收入新增
    bookCount.setCount(bookCount.getCount() + bookStore.getPrice() * store);
    int addCount = bookCountDao.addCount(bookCount);
    }
    }
  • 测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Demo03JdbcTxTest {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-jdbc.xml");
    JdbcTemplate template = context.getBean(JdbcTemplate.class);

    @Test
    public void TomBayJava() throws Exception{
    BookService bookService = context.getBean(BookService.class);
    bookService.updateBayBook("Tom", "java", 2);
    }
    }
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2022 xiaoliuxuesheng
  • PV: UV:

老板,来一杯Java

支付宝
微信