【尚硅谷】SSM框架之SSM学习笔记


MyBatis

   MyBatis简介

    MyBatis历史

  • MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github
     
  •  iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

MyBatis特性

  1. MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  2. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  4. MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

MyBatis下载 

  • MyBatis下载地址

搭建MyBatis

准备工作

  • IDEA
  • Maven
  • Mysql
  • sqlyog
  • mybatis

创建Maven工程

  1. 创建项目命名SSM

  2. 创建模块命名为MyBatis_HelloWorld

  3. 打包方式设置为jar

    jar

  4. 引入依赖

    
    	
    	
    		org.mybatis
    		mybatis
    		3.5.7
    	
    	
    	
    		junit
    		junit
    		4.12
    		test
    	
    	
    	
    		mysql
    		mysql-connector-java
    		5.1.3
            
    		
    
    

创建数据库

打开sqlyog创建数据库ssm并创建表t_user

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `gender` char(1) DEFAULT NULL,
  `emall` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

创建实体类

  1.  实体类创建到src的main目录中的java目录中

    【尚硅谷】SSM框架之SSM学习笔记_第1张图片

  2. 创建User实体类并提供构造器与Get Set 方法
    private Integer id;
        private String username;
        private String password;
        private Integer age;
        private String gender;
        private String email;
    
        public User() {
        }
    
        public User(Integer id, String username, String password, Integer age, String gender, String email) {
            this.id = id;
            this.username = username;
            this.password = password;
            this.age = age;
            this.gender = gender;
            this.email = email;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }

 创建MyBatis的核心配置文件

  • 取名可以任意但通常习惯命名为mybatis-config.xml  当整合spring后这个配置文件可以省略,所以大家操作时可以直接复制,粘贴。
  • 核心配置文件主要作用是配置连接数据库的环境以及MyBatis的全局配置信息
  • 核心配置文件存放的位置是src\main\resources目录下
  • 核心配置文件中的标签必须按照固定的顺序(有的标签可以不写,但顺序一定不能乱):
    properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers
    
    
    
        
        
            
            
                
                
                
                
                    
                    
                    
                    
                    
                    
                    
                    
                
            
        
        
        
             
            
         
        
    
    

创建mapper接口

 MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类。

public interface UserMapper {

    int insertUser();
}

创建MyBatis的映射文件

在resources目录下创建mappers目录在mappers目录下创建UserMapper.xml配置文件





    
    
    
        insert  into t_user values (null,'admin','123456',23,'男','[email protected]')
    

创建测试类

如果报错的话就是没有把mybatis添加到库里面

package com.seven.mybatis;

import com.seven.mybatis.mapper.UserMapper;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;


/**
 * @name hk
 * @time 2022-08-31-17:59
 */
public class MyBatisTest {
    @Test
    public void test() throws IOException {
        //获取核心配置文件的输入流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        //获取SQL的会话对象SqlSession,是MyBatis提供的操作数据库的对象
        SqlSession sqlSession = build.openSession();
        //获取UserMapper代理实现类对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.insertUser();
        System.out.println(i);
        sqlSession.close();

    }
}

查看sqlyog是否添加成功

                                               2022/8/31                    20:11:00


优化MyBatis框架

   //获取核心配置文件的输入流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        //获取SQL的会话对象SqlSession()不会自动提交事务,是MyBatis提供的操作数据库的对象
        //SqlSession sqlSession = build.openSession();
        //获取SQL的会话对象SqlSession(true)会自动提交事务,是MyBatis提供的操作数据库的对象
        SqlSession sqlSession = build.openSession(true);
        //获取UserMapper代理实现类对象
        //UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //提供sql以及唯一标识找到sql并执行,唯一标识是namespace.sqlId
        int i = sqlSession.insert("com.seven.mybatis.mapper.UserMapper.insertUser");
        //int i = mapper.insertUser();
        System.out.println(i);
        //提交事务
        //sqlSession.commit();
        sqlSession.close();

添加log4j日志文件

  • 在pom.xml里添加配置

      
            
                log4j
                log4j
                1.2.17
            
        
  •  在resources里创建log4j.xml

    
    
    
        
            
            
                
            
        
        
            
        
        
            
        
        
            
            
        
    

封装获取Session

package com.seven.mybatis.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;

/**
 * @name hk
 * @time 2022-09-01-15:59
 */
public class SqlSessionUtil {
    public static SqlSession getSqlSession(){
        SqlSession sqlSession =null;
        try {
            //获取核心配置文件的输入流
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            //获取SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //获取SqlSessionFactory对象
            SqlSessionFactory build = builder.build(is);
            //获取SqlSession对象
            sqlSession = build.openSession(true);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }
}

测试修改用户


    
        update t_user set gender = '女' where id =2
    
 public void upData(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.upDateUser();
        sqlSession.close();
    }

测试删除用户


    
        delete from t_user where id =3
    
public void delete(){
        SqlSession session = SqlSessionUtil.getSqlSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        mapper.deleteUser();
        session.close();
    }

 测试查询用户

  • 查询单行信息

  1. 
        
        
  2. public void getUserById(){
            SqlSession session = SqlSessionUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            User userById = mapper.getUserById();
            System.out.println(userById);
            session.close();
        }
  •  查询全部信息

  1. 
        
  2.   public void getAllUser(){
            SqlSession session = SqlSessionUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            List allUser = mapper.getAllUser();
            allUser.forEach(System.out::println);
            session.close();
        }
  3. 【尚硅谷】SSM框架之SSM学习笔记_第2张图片

MyBatis核心配置文件

  • typeAliases标签


		
		
		
		
		
	
  • environments标签

    
        
        
  • transactionManager标签

 
            
  • properties标签


             
  •  dateSource标签


            
  • property标签


                
                
                
                
                
                
                
  • mapper标签


        
        
        
        

    

不好意思!开学比较忙马上更新9.22


MyBatis获取

  • #{}获取 #{}的本质就是占位符赋值 (自动加单引号)
  • ${}获取${}的本质就是字符串拼接(需要手动添加单引号)

若mapper接口方法的参数为多个字面量类型  

此时mybatis会把参数放到map集合中,以两种方式存储

  • arg1,arg2...为键,以参数为值。
  • param1,param2...为键,以参数为值。

因此只需要通过#{}和${}访问map集合的键,就可以获取相对应的值,一定要注意${}的单引号问题。

若mapper接口方法的参数为map集合类型的参数

只需通过#{}和${}访问map集合的键,就可以获得相对应的值,一定要注意${}的单引号问题。

若mapper接口方法的参数为实体类类型的参数

只需要通过#{}和${}访问实体类中的属性名,就可以获得相对应的属性值,一定要注意${}的单引号问题。

可以在mapper接口方法的参数上设置@param注解

此时mybatis会把这些参数放到map中,以两种方式进行存储。

  • 以@param注解的value属性值为键,以参数为值。
  • 以param1,param2...为键,以参数为值。

只需要通过#{}和${}访问map集合的键,就可以获取相应的值,一定要注意${}的单引号问题

MyBatis查询

  • 根据ID获取User对象

     /**
         * 通过id获取用户数据
         * @param id
         * @return
         */
        User  getUserById(@Param("id") Integer id);
      
    
            
  • 查询所有t_user表的数据

    /**
         * 获取所有用户数据
         * @return
         * 
         * 如果获取表中所有的数据需要list集合接收
         * 
         */
    
        List getAllUser();
    /**
         * 获取所有用户数据
         * @return
         * 如果用实体类对象接收则会报错 报错信息为
         * org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null)                      
         * to be returned by selectOne(), but found: 5
         *  期望返回一个结果发现返回5条数据
         */
        User getAllUser();
    
            
    
            
  • 利用map集合获取数据

    /**
         * 用map集合获取数据
         * @param id
         * @return
         * 以字段名为键 以字段的值为值 如果某一字段值为null 则不会放到map集合中
         */
        Map getAllUserByMap(@Param("id") Integer id);
     
            

  •  利用map集合获取全部数据

     /**
         * 用map集合获取数据mapKey为键 以获取到的数据为值
         * @return
         */
    
        @MapKey("id")
        Map getAllUserByMaps();
     
            

    2022年9月25日 星期日 23:20:55 


 特殊SQL的执行

模糊查询

  • /**
    * 测试模糊查询
    * @return
    */
     
    
    List testMohu(@Param("mohu") String mohu);
  • 
    
     
    
    

批量删除

  • /**
    * 批量删除 
    * @param ids 
    * @return 
    */ 
    int deleteMore(@Param("ids") String ids);
  •  
    
     
                
        delete from t_user where id in (${ids})
    

动态设置表名

  • /**
    * 动态设置表名,查询所有的用户信息 
    * @param tableName 
    * @return 
    */ 
    
    List getAllUser(@Param("tableName") String tableName);
  • 
    
    
    

获取自增的主键 

  • /**
    * 添加用户信息 
    * @param user 
    * @return 
    */ 
    
    int insertUser(User user);
  •  
    
    
     
         insert into t_user values(null,#{username},#{password},#{age},#{sex}) 
    

自定义映射resultMap

 resultMup处理字段和属性

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射
 
 
 
 
 
 
  
 
若字段名和实体类中的属性名不一致, 但是字段名符合数据库的规则(使用_) ,实体类中的
属性
名符合 Java 的规则(使用驼峰)
此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系
a> 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
b> 可以在 MyBatis 的核心配置文件中设置一个全局配置信息 mapUnderscoreToCamelCase ,可
以在查询表中数据时,自动将 _ 类型的字段名转换为驼峰
例如:字段名 user_name ,设置了 mapUnderscoreToCamelCase ,此时字段名就会转换为
userName

多对一映射处理

查询员工信息及员工所对应的部门信息 

  • 级联方式处理映射关系
     
     
     
     
     
     
     
     
    
     
    
    

  •  使用association处理映射关系
     
    
         
         
         
         
        
         
             
            
         
     
    
    
     
    

分步查询

  1. 查询员工信息
    /**
    * 通过分步查询查询员工信息 
    * @param eid 
    * @return 
    */ 
    
    Emp getEmpByStep(@Param("eid") int eid);
     
    
     
     
     
     
     
    
     
     
     
     
    
  2. 根据员工所对应的部门id查询部门信息
    /**
    * 分步查询的第二步: 根据员工所对应的did查询部门信息 
    * @param did 
    * @return 
    */ 
    
    Dept getEmpDeptByStep(@Param("did") int did);
     
    
    

一步一步查先查第一步查到emp表的信息然后mybatis会自动调用 select标签的接口并把查询到的数据通过column 内的遍历传进getEmpDeptByStep的参数中执行第二个查询语句并返回dept类型的数据(大概就是这个意思,有错误欢迎告诉我我会及时更正 一起加油!!)

  • 延迟加载
       
            
            
            
            
            
            
        
分步查询的优点:可以实现延迟加载
但是必须在 核心配置文件中 设置全局配置信息:
lazyLoadingEnabled :延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading :当开启时,任何方法的调用都会加载该对象的所有属性。
否则,每个属性会按需加载此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql
此时可通过 association 和collection中的 fetchType 属性设置当前的分步查询是否使用延迟加载, fetchType="lazy( 延迟加载)|eager( 立即加载 )"

动态SQL

MyBatis框架的动态SQL技术是一种根据特定条件动态拼接SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

if标签

if标签可通过test属性的表达式进行判断,若判断表达式结果为true,则标签中的内容会执行;反之标签中的内容不会执行。

 

休息休息QAQ 2022年10月6日18:11:11 


where标签

  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
  • 注意:where标签不能去掉条件最后多余的and
 

trim标签

  • prefix、suffix:在标签中内容前面或后面添加指定内容
  • prefixOverrides、suffixOverrides:在标签中内容前面或后面去掉指定内容
 

 choose、when、otherwise 标签

相当于java中的if...else if...else       when至少设置一个,otherwise最多设置一个

foreach 标签

循环添加


        insert into t_emp values
        
        
            (null,#{emp.empName},#{emp.age},#{emp.gender},null)
        
    

循环删除


 
        
        
            #{empId}
        -->


        
        delete from t_emp where
        
            emp_id = #{empId}
        
    

sql标签

可以记录一段sql,在需要用的地方使用include标签进行引用


        emp_id,emp_name,age,gender,dept_id
    






MyBatis的缓存

MyBatis的一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

使一级缓存失效的四种情况:

  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存

MyBatis的二级缓存

二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
  • 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
  • 在映射文件中设置标签
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

二级缓存的相关配置

看不懂的话 跳过

mapper 配置文件中添加的 cache 标签可以设置一些属性:
  • eviction属性:缓存回收策略,默认的是 LRU。 LRU(Least Recently Used)                        最近最少使用的:移除最长时间不被使用的对象。                                                                FIFO(First in First out先进先出:按对象进入缓存的顺序来移除它们。                    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。                                  WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  • flushInterval属性:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
  • size属性:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读, true/false                                                                                    true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。                                                                                                 false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false

 MyBatis缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession 关闭之后,一级缓存中的数据会写入二级缓存

整合第三方缓存EHCache

  1.  添加依赖
     
     
    org.mybatis.caches 
    mybatis-ehcache 
    1.2.1  
     
     
    ch.qos.logback 
    logback-classic 
    1.2.3 
    
  2. 各jar包功能【尚硅谷】SSM框架之SSM学习笔记_第3张图片
  3. 创建EHCache的配置文件ehcache.xml
     
     
     
     
     
    
     
    
  4.  设置二级缓存的类型
  5. 加入logback日志 
    
    
      
          
          
          
          [%d{HH:mm:ss.SSS}][%-5level][%thread][%logger]
    [%msg]%n
        
      
    
      
      
            
      
    
      
  6. EHCache配置文件说明【尚硅谷】SSM框架之SSM学习笔记_第4张图片

 麻烦吗? 兄弟们 麻烦就接着看 最牛逼的要来了  MyBatis的逆向工程YYDS

MyBatis的逆向工程

正向工程:先创建 Java 实体类,由框架负责根据实体类生成数据库表。 Hibernate 是支持正向工
程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
  • Java实体类
  • Mapper接口
  • Mapper映射文件

 创建逆向工程的步骤

  1. 添加依赖和插件
     
        
            
                org.mybatis
                mybatis
                3.5.7
            
            
            
                junit
                junit
                4.12
                test
            
    
            
            
                log4j
                log4j
                1.2.17
            
    
            
                mysql
                mysql-connector-java
                8.0.16
            
            
                com.github.pagehelper
                pagehelper
                5.2.0
            
        
    
        
        
    
            
            
    
                
                
                    org.mybatis.generator
                    mybatis-generator-maven-plugin
                    1.3.0
    
                    
                    
    
                        
                        
                            org.mybatis.generator
                            mybatis-generator-core
                            1.3.2
                        
    
                        
                        
                            mysql
                            mysql-connector-java
                            8.0.16
                        
                    
                
            
        
  2. 创建MyBatis的核心配置文件 【尚硅谷】SSM框架之SSM学习笔记_第5张图片

  3. 创建逆向工程的配置                                                                                                                                          文件文件名必须是:generatorConfig.xml   
    
    
    
        
        
            
            
                            
    
            
            
            
                
                
            
            
            
                
            
            
            
                
            
            
            
            
            
  4. 执行 MBG 插件的generate目标 【尚硅谷】SSM框架之SSM学习笔记_第6张图片

  5. 效果图

    【尚硅谷】SSM框架之SSM学习笔记_第7张图片

  6.  

    分页插件

    分页插件的使用步骤

    1. 添加依赖
      dependency>
        com.github.pagehelper  pagehelper
        5.2.0
      
    2. 配置分页插件
      
      
        
        

    分页插件的使用

    • 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能pageNum:当前页的页码                        pageSize:每页显示的条数
    • 在查询获取 list 集合之后                                                                                                            使用 PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关   数据                    list:分页之后的数据                   navigatePages:导航分页的页码数
    • 分页相关数据
      PageInfo{
      pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
      list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
      pages=8, reasonable=false, pageSizeZero=false},
      prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
      hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
      navigatepageNums=[4, 5, 6, 7, 8]
      }
      pageNum :当前页的页码
      pageSize :每页显示的条数
      size :当前页显示的真实条数
      total :总记录数
      pages :总页数
      prePage :上一页的页码
      nextPage :下一页的页码
      isFirstPage/isLastPage :是否为第一页 / 最后一页
      hasPreviousPage/hasNextPage :是否存在上一页 / 下一页
      navigatePages :导航分页的页码数
      navigatepageNums :导航分页的页码, [1,2,3,4,5]

    Mybatis完结撒花  spring 已经看完 准备复习+笔记 2022.10.15/23:59:15 

    Spring

    Spring简介

    sping概述

    官网地址:https://spring.io/
    • Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用Spring 框架来创建性能好、易于测试、可重用的代码。
    • Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 6 月首次在 Apache 2.0 许可下发布。
    • Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。
    • Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO编程模型来促进良好的编程实践。

    Spring家族

    项目列表:https://spring.io/projects 

    Spring Framework

    Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。

    Spring Framework特性

    • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
    • 控制反转IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。(重点)
    • 面向切面编程AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。(重点)
    • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。(理解)
    • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
    • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
    • 一站式:在 IOC AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。

     Spring Framework五大功能模块

    功能模块
    功能介绍
    Core Container
    核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
    AOP&Aspects        
    面向切面编程。
    Testing
    提供了对 junit TestNG 测试框架的整合。
    Data Access/Integration
    提供了对数据访问 / 集成的功能。
    Spring MVC
    提供了面向 Web 应用程序的集成功能。

    IOC

    IOC容器

    IOC思想

    IOCInversion of Control,翻译过来是反转控制

    1. 获取资源的传统方式
      自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
      在应用程序中的组件需要获取资源时,传统的方式是组件 主动 的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
    2. 反转控制方式获取资源
      点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。
      反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向 —— 改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动 形式。
    3. DI
      DI Dependency Injection ,翻译过来是 依赖注入
      DI IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如: setter 方法)接受来自于容器 的资源注入。相对于IOC 而言,这种表述更直接。
      所以结论是: IOC 就是一种反转控制的思想,而 DI 是对 IOC 的一种具体实现。

     IOC容器在Spring中的实现

    Spring IOC 容器就是 IOC 思想的一个落地的产品实现。 IOC 容器中管理的组件也叫做 bean 。在创建bean 之前,首先需要创建 IOC 容器。 Spring 提供了 IOC 容器的两种实现方式:
    1. BeanFactory
      这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
    2. ApplicationContext
      BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用
      ApplicationContext 而不是底层的 BeanFactory。
    3. ApplicationContext的主要实现类

      【尚硅谷】SSM框架之SSM学习笔记_第8张图片

      类型名
      简介
      ClassPathXmlApplicationContext
      通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
      FileSystemXmlApplicationContext
      通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
      ConfigurableApplicationContext
      ApplicationContext 的子接口,包含一些扩展方法
      refresh() 和 close() ,让 ApplicationContext 具有启动、
      关闭和刷新上下文的能力。
      WebApplicationContext
      专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对
      象,并将对象引入存入 ServletContext 域中。

       基于XML管理bean

      实验一:入门案例

      1. 创建Maven Module
      2. 引入依赖
        
         
          
            org.springframework
            spring-context
            5.3.1
          
          
          
            junit
            junit
           4.12    test
          
        

        【尚硅谷】SSM框架之SSM学习笔记_第9张图片

      3. 创建类HelloWorld
        public class HelloWorld {
        
            public void sayHello(){
                System.out.println("hello,spring");
            }
        
        }

      4. 创建Spring的配置文件【尚硅谷】SSM框架之SSM学习笔记_第10张图片【尚硅谷】SSM框架之SSM学习笔记_第11张图片
      5. Spring的配置文件中配置bean
        
        
      6. 创建测试类测试
            @Test
            public void test(){
                //获取IOC容器
                ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
                //获取IOC容器中的bean
                HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");
                helloworld.sayHello();
            }
        
        
      7. 思路【尚硅谷】SSM框架之SSM学习笔记_第12张图片
      8.  注意

        Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要 无参构造器时,没有无参构造器,则会抛出下面的异常: 
        org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloworld' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed
        to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld. ()

      实验二:获取bean

      1. 方式一:根据id获取

        由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。 上个实验中我们使用的就是这种方式。

      2. 方式二:根据类型获取
         @Test
            public void test(){
                //获取IOC容器
                ApplicationContext ioc =
        new ClassPathXmlApplicationContext("applicationContext.xml");
                //获取IOC容器中的bean对象
               HelloSpring helloSpring = 
        (HelloSpring) ioc.getBean(HelloSpring.class);
              helloSpring.seyHello();
        
            }
      3. 根据id和类型

          @Test
            public void test(){
                //获取IOC容器
                ApplicationContext ioc =new ClassPathXmlApplicationContext("applicationContext.xml");
                //获取IOC容器中的bean对象
               HelloSpring helloSpring = (HelloSpring) ioc.getBean("helloword",HelloSpring.class);
              helloSpring.seyHello();
        
            }
      4. 注意
        当根据类型获取 bean 时,要求 IOC 容器中指定类型的 bean 有且只能有一个
        IOC 容器中一共配置了两个:
        
        
        根据类型获取时会抛出异常:
        org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.atguigu.spring.bean.HelloWorld' available: expected single matching bean but found 2: helloworldOne,helloworldTwo
      5. 扩展
        如果组件类实现了接口,根据接口类型可以获取 bean 吗?
        可以,前提是 bean 唯一
        如果一个接口有多个实现类,这些实现类都配置了 bean ,根据接口类型可以获取 bean 吗?
        不行,因为 bean 不唯一
      6. 结论
        根据类型来获取 bean 时,在满足 bean 唯一性的前提下,其实只是看:
        『对象instanceof 指定的类型』的返回结果,只要返回的是true 就可以认定为和类型匹配,能够获取到。

       实验三:依赖注入之setter注入

      1. 创建学生类Student
        public class Student {
            private String name;
            private int age;
            private Dept dept;
        
            public Student(String name, int age, Dept dept) {
                this.name = name;
                this.age = age;
                this.dept = dept;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            public int getAge() {
                return age;
            }
        
            public void setAge(int age) {
                this.age = age;
            }
        
            public Dept getDept() {
                return dept;
            }
        
            public void setDept(Dept dept) {
                this.dept = dept;
            }
        
            @Override
            public String toString() {
                return "Student{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                        ", dept=" + dept +
                        '}';
            }
        
            public Student() {
            }
        
            public Student(String name, int age) {
                this.name = name;
                this.age = age;
            }

      2. 配置bean时为属性赋值
        
            
                
                
            
      3. 测试
         @Test
            public void test() {
                                        //bean.xml为配置注入的spring配置文件
                ApplicationContext ioc = new ClassPathXmlApplicationContext("been.xml");
                Student student = ioc.getBean("student", Student.class);
                System.out.println(student);
            }

        2022.10.16     14:54:25  休息休息

      实验四:依赖注入之构造器注入

      1. Student类中添加有参构造
        public Student(Integer id,String name,Integer age,String sex){  
          this.id=id;
          this.name=name;
          this.age=age;
          this.sex=sex;
        }
      2. 配置bean
          
        
          
          
          
          
        
      3. 测试
        @Test
        public void testDIBySet(){
          ApplicationContextac=
                new ClassPathXmlApplicationContext("spring-di.xml"); 
         StudentstudentOne = ac.getBean("studentTwo",Student.class);
          System.out.println(studentOne);
        }

      实验五:特殊值处理

      1. 字面量赋值
        什么是字面量?
        int a = 10;
        声明一个变量 a ,初始化为 10 ,此时 a 就不代表字母 a 了,而是作为一个变量的名字。当我们引用 a
        的时候,我们实际上拿到的值是 10
        而如果 a 是带引号的: 'a' ,那么它现在不是一个变量,它就是代表 a 这个字母本身,这就是字面
        量。所以字面量没有引申含义,就是我们看到的这个数据本身。
        
        
      2. null
          
        
        
        注意:
        以上写法,为 name 所赋的值是字符串 null
      3. xml实体
        
        
        
      4. CDATA节
        
          
            
            
          
          
        

      实验六:为类类型属性赋值

      1. 创建班级类Clazz
        public class Clazz {
            private Integer clazzId;
            private String clazzName;
            public Integer getClazzId() {
            return clazzId;
                    }
            public void setClazzId(Integer clazzId) {
            this.clazzId = clazzId;
                    }
            public String getClazzName() {
            return clazzName;
                    }
            public void setClazzName(String clazzName) {
            this.clazzName = clazzName;
                    }
            @Override
            public String toString() {
        
                    return "Clazz{" +
                    "clazzId=" + clazzId +
                        ", clazzName='" + clazzName + '\'' +
                            '}';
            }
            public Clazz() {
                     }
            public Clazz(Integer clazzId, String clazzName) {
                this.clazzId = clazzId;
                this.clazzName = clazzName;
                    }
        }
      2. 修改Student类
        Student 类中添加以下代码:
        private Clazz clazz;
        public Clazz getClazz() {
        return clazz;
        }
        public void setClazz(Clazz clazz) {
        this.clazz = clazz;
        }
      3. 方式一:引用外部已声明的bean
        
        
        
        
        Student 中的 clazz 属性赋值:
        
        
        
        
        
        
        
        
        错误演示:
        
        
        
        
        
        
        
        如果错把 ref 属性写成了 value 属性,会抛出异常: Caused by: java.lang.IllegalStateException:
        Cannot convert value of type 'java.lang.String' to required type
        'com.atguigu.spring.bean.Clazz' for property 'clazz': no matching editors or conversion
        strategy found
        意思是不能把 String 类型转换成我们要的 Clazz 类型,说明我们使用 value 属性时, Spring 只把这个属性看做一个普通的字符串,不会认为这是一个bean id ,更不会根据它去找到 bean 来赋值
      4. 方式二:内部bean
        
        
        
        
        
        
        
        
        
        
        
        
        
        
      5. 方式三:级联属性赋值
        
        
        
        
        
        
        
        
        
        

      各位1024节快乐   2022.10.24 22:10:20  这次断更主要是因为最近参加考试事情有点多 等我闲下来马上更新

      实验七:为数组类型属性赋值

      1. 修改Student类                                                                                                                Student类中添加以下代码:
        private String[] hobbies;
        public String[] getHobbies() {
        return hobbies;
        }
        public void setHobbies(String[] hobbies) {
        this.hobbies = hobbies;
        }
      2. 配bean                                                                                                                          
          
                
                
            
        
            
                
                
                
                
                
                    
                        抽烟
                        喝酒
                        烫头
                    
                
            
            
                
            
      3. 测试类
         @Test
            public void testScope(){
                ApplicationContext ioc  = new ClassPathXmlApplicationContext("spring-scope.xml");
                Student bean = ioc.getBean("studentFour",Student.class);
        // 我重写了tostring方法所以可以直接输出
                System.out.println(bean);
            }

      4. 效果

       实验八:引入外部属性文件

      1. 加入依赖
        
        
        mysql
        mysql-connector-java
        8.0.16
        
        
        
        com.alibaba
        druid
        1.0.31
        
      2. 创建外部属性文件                                                                                

        【尚硅谷】SSM框架之SSM学习笔记_第13张图片

        jdbc.driver=com.mysql.cj.jdbc.Driver
        jdbc.url=jdbc:mysql://localhost:13306/ssm?serverTimezone=UTC
        jdbc.username=root
        jdbc.password=asd1230.

        这是配置自己的数据库信息

      3. 引入并且配置bean
        
        
        
        
        
        
              
              
              
              
        
      4. 测试类
            @Test
            public void testConn() throws SQLException {
                ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml");
                DruidDataSource bean = ioc.getBean(DruidDataSource.class);
                System.out.println(bean.getConnection());
            }

        注意xml名字改成自己写的,如果连接失败看一下是不是自己的信息填错了或者数据库开了没

      5. 输出结果【尚硅谷】SSM框架之SSM学习笔记_第14张图片

       实验九:bean的作用域

      1. 概念                                                                                                                               在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
        取值
        含义
        创建对象的时机
        singleton (默认)
        IOC 容器中,这个 bean 的对象始终为单实例
        IOC 容器初始化时
        prototype
        这个 bean IOC 容器中有多个实例
        获取 bean
        如果是在 WebApplicationContext 环境下还会有另外两个作用域(但不常用):
        取值
        含义
        request
        在一个请求范围内有效
        session
        在一个会话范围内有效
      2. 创建类User
        public class User {
        private Integer id;
        private String username;
        private String password;
        private Integer age;
        public User() {
        }
        public User(Integer id, String username, String password, Integer age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        }
        public Integer getId() {
        return id;
        }
        public void setId(Integer id) {
        this.id = id;
        }
        public String getUsername() {
        return username;
        }
        public void setUsername(String username) {
        this.username = username;
        }
        public String getPassword() {
        return password;
        }
        public void setPassword(String password) {
        this.password = password;
        }
        public Integer getAge() {
        return age;
        }
        public void setAge(Integer age) {
        this.age = age;
        }
        @Override
        public String toString() {
        return "User{" +
        "id=" + id +
        ", username='" + username + '\'' +
        ", password='" + password + '\'' +
        ", age=" + age +
        '}';
        }
        }
      3. 配置 bean
        
        
           
           
        
      4. 测试
          @Test
            public void testScope(){
                ApplicationContext ioc  = new ClassPathXmlApplicationContext("spring-scope.xml");
                Student bean = ioc.getBean(Student.class);
                Student bean1 = ioc.getBean(Student.class);
                System.out.println(bean);
                System.out.println(bean1);
                System.out.println(bean==bean1);
            }

         证明容器获取的同一个对象单例模式

        【尚硅谷】SSM框架之SSM学习笔记_第15张图片

        如果把配置文件中scope的值更改为prototype则效果如下

        【尚硅谷】SSM框架之SSM学习笔记_第16张图片

    2022年11月21日0:25:00 

    因为学校所在地疫情较为严重 所以学校让请假回家,今天刚到家,尽量每天持续不断的更新!一起加油!!

    实验十:bean的生命周期

    1. 具体的生命周期过程     
      1. bean 对象创建(调用无参构造器)
      2. bean对象设置属性
      3. bean对象初始化之前操作(由bean的后置处理器负责)
      4. bean对象初始化(需在配置bean时指定初始化方法)
      5. bean对象初始化之后操作(由bean的后置处理器负责)
      6. bean对象就绪可以使用
      7. bean对象销毁(需在配置bean时指定销毁方法)
      8. IOC容器关闭
    2. 修改类User
      public class User {
      private Integer id;
      private String username;
      private String password;
      private Integer age;
      public User() {
      System.out.println("生命周期:1、创建对象");
      }
      public User(Integer id, String username, String password, Integer age) {
      this.id = id;
      this.username = username;
      this.password = password;
      this.age = age;
      }
      public Integer getId() {
      return id;
      }
      public void setId(Integer id) {
      System.out.println("生命周期:2、依赖注入");
      this.id = id;
      }
      public String getUsername() {
      return username;
      }
      public void setUsername(String username) {
      this.username = username;
      }
      public String getPassword() {
      return password;
      }
      public void setPassword(String password) {
      this.password = password;
      }
      public Integer getAge() {
      return age;
      }
      public void setAge(Integer age) {
      this.age = age;
      }
      public void initMethod(){
      System.out.println("生命周期:3、初始化");
      }
      public void destroyMethod(){
      System.out.println("生命周期:5、销毁");
      }
      @Override
      public String toString() {
      return "User{" +
      "id=" + id +
      ", username =='" + username + '\'' +
      ", password =='" + password + '\'' +
      ", age=" + age +
      '}';
      }
      }
      注意其中的initMethod()destroyMethod(),可以通过配置bean指定为初始化和销毁的方法
    3. 配置 bean
      
      
      
      
      
      
      
      
    4. 测试
      @Test
      public void testLife(){
      ClassPathXmlApplicationContext ac = new
      ClassPathXmlApplicationContext("spring-lifecycle.xml");
      User bean = ac.getBean(User.class);
      System.out.println("生命周期:4、通过IOC容器获取bean并使用");
      ac.close();
      }
    5. bean的后置处理器
      bean 的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现 BeanPostProcessor 接口,且配置到 IOC 容器中,需要注意的是, bean 后置处理器不是单独针对某一个 bean 生效,而是针对 IOC 器中所有 bean 都会执行
      创建 bean 的后置处理器:
      package com.atguigu.spring.process;
      import org.springframework.beans.BeansException;
      import org.springframework.beans.factory.config.BeanPostProcessor;
      public class MyBeanProcessor implements BeanPostProcessor {
      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
      System.out.println("☆☆☆" + beanName + " = " + bean);
      return bean;
      }
      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName)
      throws BeansException {
      System.out.println("★★★" + beanName + " = " + bean);
      return bean;
      }
      }
      IOC 容器中配置后置处理器:
      
      

    实验十三:FactoryBean 

    1. 简介
      FactoryBean Spring 提供的一种整合第三方框架的常用机制。和普通的 bean 不同,配置一个FactoryBean 类型的 bean ,在获取 bean 的时候得到的并不是 class 属性中配置的这个类的对象,而是 getObject() 方法的返回值。通过这种机制, Spring 可以帮我们把复杂组件创建的详细过程和繁琐细节都 屏蔽起来,只把最简洁的使用界面展示给我们。
      将来我们整合 Mybatis 时, Spring 就是通过 FactoryBean 机制来帮我们创建 SqlSessionFactory 对象的。
    2. 创建类UserFactoryBean

      public class UserFactoryBean implements FactoryBean {
      @Override
      public User getObject() throws Exception {
      return new User();
      }
      @Override
      public Class getObjectType() {
      return User.class;
      }
      }
    3. 配置 bean
    4. 测试  
      @Test
      public void testUserFactoryBean(){
      //获取IOC容器
      ApplicationContext ac = new ClassPathXmlApplicationContext("springfactorybean.xml");
      User user = (User) ac.getBean("user");
      System.out.println(user);
      }

    实验十四:基于xml的自动装配

    自动装配:
    根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
    1. 场景模拟
       创建类 UserController
      public class UserController {
      private UserService userService;
      public void setUserService(UserService userService) {
      this.userService = userService;
      }
      public void saveUser(){
      userService.saveUser();
      }
      }
      创建接口 UserService
      public interface UserService {
      void saveUser();
      }
      创建类 UserServiceImpl 实现接口 UserService
      public class UserServiceImpl implements UserService {
      private UserDao userDao;
      public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
      }
      @Override
      public void saveUser() {
      userDao.saveUser();
      }
      }
      创建接口 UserDao
      public interface UserDao {
      void saveUser();
      }
      创建类 UserDaoImpl 实现接口UserDao
      public class UserDaoImpl implements UserDao {
      @Override
      public void saveUser() {
      System.out.println("保存成功");
      }
      }
    2. 配置 bean
      使用 bean 标签的 autowire 属性设置自动装配效果
      自动装配方式:byType
      byType :根据类型匹配 IOC 容器中的某个兼容类型的 bean ,为属性自动赋值
      若在 IOC 中,没有任何一个兼容类型的 bean 能够为属性赋值,则该属性不装配,即值为默认值 null
      若在 IOC 中,有多个兼容类型的 bean 能够为属性赋值,则抛出异常
      NoUniqueBeanDefinitionException
      
      
      
      
      
      自动装配方式: byName
      byName :将自动装配的属性的属性名,作为 bean id IOC 容器中匹配相对应的 bean 进行赋值
      
      
      
      
      
      
      
      
      
    3. 测试

      @Test
      public void testAutoWireByXML(){
      ApplicationContext ac = new ClassPathXmlApplicationContext("autowirexml.xml");
      UserController userController = ac.getBean(UserController.class);
      userController.saveUser();
      }

    通常来讲 学完springboot后xml的使用较少

    基于注解管理bean

    1. 标记与扫描

    1. 注解

    理解为备注,具体功能为让框架检测到标注的位置,并执行注解的类型来执行。注解本身不能执行

         2. 扫描

    spring需要通过扫描来查看注解的位置进而执行。

         3.创建Maven Module

    
      
      
        org.springframework
        spring-context
        5.3.1
      
    
      
        junit
        junit  
        4.12
        test
      
    

          4.创建spring配置文件

    【尚硅谷】SSM框架之SSM学习笔记_第17张图片

           5.标识组件的常用注解

    • @Component:将类标识为普通组件 (相对用的较少类似@bean)        
      • @bean :将方法标记为一个spring组件
    • @Controller:将类标识为控制层组件 
    • @Service:将类标识为业务层组件
    • @Repository:将类标识为持久层组件

    【尚硅谷】SSM框架之SSM学习笔记_第18张图片

     通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

    注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记

    2. 基于注解的自动装配

    @Autowired注解(自动装配)

    在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。

    @Controller
    public class UserController {
    
    @Autowired
    private UserService userService;
    
    public void saveUser(){
        userService.saveUser();
        }
    }

    具体流程为在spring容器中搜索UserService的bean(当然要给UserService这个类加上注解不然会扫描不到)然后注入给当前标注的属性

    AOP

    概述:

    AOP Aspect Oriented Programming )是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。 (简单来说就是在不修改源码的情况下添加新的功能类似外挂)  比如:拦截器

    相关术语

    1. 横切关注点 

      从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
      这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
      对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
    2. Advice通知

      AOP在特定的切入点上执行的增强处理,有before(前置)、after(后置)、afterReturning(最终)、afterThrowing(异常)、around(环绕)。

    3. Aspect(切面)

       通常是一个类,里面可以定义切入点和通知。

    4.  JoinPoint(连接点)

      程序执行过程中明确的点,一般是方法的调用,被拦截到的点。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
    5.  Pointcut(切入点)

      带有通知的连接点,在程序中主要体现在书写切入点表达式。
    6.  目标对象(Target Object)

      包含连接点的对象,也被称作被通知或被代理对象,POJO。
    7.  AOP代理(AOP Proxy)

      AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

     作用

    • 简化代码:把方法中固定位置的重复的代码 抽取 出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
    • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就 被切面给增强了。

    基于注解的AOP 

    技术说明

    【尚硅谷】SSM框架之SSM学习笔记_第19张图片

    •  动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。

    • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。

    • AspectJ:本质上是静态代理,将代理逻辑织入被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

     准备工作

    1. 添加依赖
              
          
              
                  org.springframework
                  spring-context
                  5.3.1
              
              
              
                  org.springframework
                  spring-aspects
                  5.3.1
              
          
          
              junit
              junit
              4.12
              test
          
      
    2. 准备被代理的目标资源
      接口:
      public interface Calculator {
          int add(int i, int j);
          int sub(int i, int j);
          int mul(int i, int j);
          int div(int i, int j);
      }
      实现类:
          @Component
          public class CalculatorPureImpl implements Calculator {
              @Override
              public int add(int i, int j) {
                  int result = i + j;
                  System.out.println("方法内部 result = " + result);
                  return result;
              }
              @Override
              public int sub(int i, int j) {
                  int result = i - j;
                  System.out.println("方法内部 result = " + result);
                  return result;
              }
              @Override
              public int mul(int i, int j) {
                  int result = i * j;
                  System.out.println("方法内部 result = " + result);
                  return result;
              }
              @Override
              public int div(int i, int j) {
                  int result = i / j;
                  System.out.println("方法内部 result = " + result);
                  return result;
              }
          }

    创建切面类并配置  

    // @Aspect表示这个类是一个切面类
    @Aspect
    // @Component注解保证这个切面类能够放入IOC容器
    @Component
    public class LogAspect {
        @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            String args = Arrays.toString(joinPoint.getArgs());
            System.out.println("Logger-->前置通知,方法名:" + methodName + ",参数:"+args);
        }
    
        @After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
        public void afterMethod(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Logger-->后置通知,方法名:" + methodName);
        }
    
        @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = " result")
    
        public void afterReturningMethod(JoinPoint joinPoint, Object result) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Logger-->返回通知,方法名:" + methodName + ",结果:"+result);
        }
    
        @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = " ex")
    
        public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
            String methodName = joinPoint.getSignature().getName();
            System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
        }
    
        @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
        public Object aroundMethod(ProceedingJoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            String args = Arrays.toString(joinPoint.getArgs());
            Object result = null;
            try {
                System.out.println("环绕通知-->目标对象方法执行之前");
    //目标对象(连接点)方法的执行
                result = joinPoint.proceed();
                System.out.println("环绕通知-->目标对象方法返回值之后");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                System.out.println("环绕通知-->目标对象方法出现异常时");
            } finally {
                System.out.println("环绕通知-->目标对象方法执行完毕");
            }
            return result;
        }
    }
    Spring 的配置文件中配置:
    
    
    
    

    各种通知

    • 前置通知:使用@Before注解标识,在被代理的目标方法执行
    • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
    • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
    • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
    • 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包
    • 括上面四种通知对应的所有位置

    切入点表达式语法

    1. 作用

      【尚硅谷】SSM框架之SSM学习笔记_第20张图片

        2.  语法细节
    • *号代替权限修饰符返回值部分表示权限修饰符返回值不限
    • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
      • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
    • 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
    • 在类名的部分,类名部分整体用*号代替,表示类名任意
    • 在类名的部分,可以使用*号代替类名的一部分
      • 例如:*Service匹配所有名称以Service结尾的类或接口
    • 在方法名部分,可以使用*号表示方法名任意
    • 在方法名部分,可以使用*号代替方法名的一部分
      • 例如:*Operation匹配所有方法名以Operation结尾的方法
    • 在方法参数列表部分,使用(..)表示参数列表任意
    • 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
    • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
    • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
    • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
      • 例如:execution(public int ..Service.*(.., int)) 正确
      • 例如:execution(* int ..Service.*(.., int)) 错误

    【尚硅谷】SSM框架之SSM学习笔记_第21张图片

     重用切入点表达式

    1.  声明
      @Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
          public void pointCut(){}
    2. 在同一个切面中使用
      @Before("pointCut()")
          public void beforeMethod(JoinPoint joinPoint){
          String methodName = joinPoint.getSignature().getName();
          String args = Arrays.toString(joinPoint.getArgs());
          System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
      }
    3. 在不同切面中使用
      @Before("com.atguigu.aop.CommonPointCut.pointCut()")
          public void beforeMethod(JoinPoint joinPoint){
          String methodName = joinPoint.getSignature().getName();
          String args = Arrays.toString(joinPoint.getArgs());
          System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
      }

     获取通知的相关信息

    1.  获取连接点信息

    1.  获取连接点信息可以在通知方法的参数位置设置 JoinPoint 类型的形参
      @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
      public void beforeMethod(JoinPoint joinPoint){
          //获取连接点的签名信息
          String methodName = joinPoint.getSignature().getName();
          //获取目标方法到的实参信息
          String args = Arrays.toString(joinPoint.getArgs());
          System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
      }
    2. 获取目标方法的返回值
      @AfterReturning 中的属性 returning ,用来将通知方法的某个形参,接收目标方法的返回值
      @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
      (..))", returning = "result")
          public void afterReturningMethod(JoinPoint joinPoint, Object result){
          String methodName = joinPoint.getSignature().getName();
          System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
      }
    3. 获取目标方法的异

      @AfterThrowing 中的属性 throwing ,用来将通知方法的某个形参,接收目标方法的异常
      @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*
      (..))", throwing = "ex")
          public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
          String methodName = joinPoint.getSignature().getName();
          System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
      }

    环绕通知

    @Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标方法的执行,目标方法的返回值一定要返回给外界调用者
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
        throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }

    切面的优先级

    相同目标方法上同时存在多个切面时,切面的优先级控制切面的 内外嵌套 顺序。
    • 优先级高的切面:外面
    • 优先级低的切面:里面

     使用@Order注解可以控制切面的优先级:

    • @Order(较小的数):优先级高
    • @Order(较大的数):优先级低

      【尚硅谷】SSM框架之SSM学习笔记_第22张图片

     声明式事务

    JdbcTemplate

    简介

    Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作 (暂时使用)

    准备工作

    1. 加入依赖
      
          
          
              org.springframework
              spring-context
              5.3.1
          
          
          
          
          
              org.springframework
              spring-orm
              5.3.1
          
          
          
              org.springframework
              spring-test
              5.3.1
          
          
          
              junit
              junit
              4.12
              test
          
          
          
              mysql
              mysql-connector-java
              8.0.16
          
          
          
              com.alibaba
              druid
              1.0.31
          
      
    2. 创建 jdbc.properties
      jdbc.user=root
      jdbc.password=atguigu
      jdbc.url=jdbc:mysql://localhost:3306/ssm
      jdbc.driver=com.mysql.cj.jdbc.Driver
    3. 配置 Spring 的配置文件
      
      
          
          
          
          
              
              
              
              
          
          
          
              
              
          
      

      测试

      1. 在测试类装配 JdbcTemplate
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration("classpath:spring-jdbc.xml")
        public class JDBCTemplateTest {
            @Autowired
            private JdbcTemplate jdbcTemplate;
        }
      2. 测试增删改功能
        @Test
        //测试增删改功能
        public void testUpdate(){
            String sql = "insert into t_emp values(null,?,?,?)";
            int result = jdbcTemplate.update(sql, "张三", 23, "男");
            System.out.println(result);
        }

    声明式事务概念

    编程式事务

    事务功能的相关操作全部通过自己编写代码来实现:
    Connection conn = ...;
    try {
        // 开启事务:关闭事务的自动提交
        conn.setAutoCommit(false);
        // 核心操作
        // 提交事务
        conn.commit();
    }catch(Exception e){
    // 回滚事务
        conn.rollBack();
    }finally{
        // 释放数据库连接
        conn.close();
    }
    编程式的实现方式存在缺陷:
    • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
    • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

    声明式事务

    既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
    封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
    • 好处1:提高开发效率
    • 好处2:消除了冗余的代码
    • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性 能等各个方面的优化
    所以,我们可以总结下面两个概念:
    • 编程式自己写代码实现功能
    • 声明式:通过配置框架实现功能

    基于注解的声明式事务

    准备工作

    1. 加入依赖
       
          
          
              org.springframework
              spring-context
              5.3.1
          
          
          
          
          
              org.springframework
              spring-orm
              5.3.1
          
          
          
          org.springframework
          spring-test
          5.3.1
              
              
                  junit
                  junit
                  4.12
                  test
              
              
              
                  mysql
                  mysql-connector-java
                  8.0.16
              
              
              
                  com.alibaba
                  druid
                  1.0.31
              
          
    2. 创建 jdbc.properties
      jdbc.user=root
      jdbc.password=123456
      jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
      jdbc.driver=com.mysql.cj.jdbc.Driver
    3. 配置 Spring 的配置文件
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    4. 创建表
      CREATE TABLE `t_book` (
          `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
          `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
          `price` int(11) DEFAULT NULL COMMENT '价格',
          `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
          PRIMARY KEY (`book_id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
      insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍
      穹',80,100),(2,'斗罗大陆',50,100);
      CREATE TABLE `t_user` (
          `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
          `username` varchar(20) DEFAULT NULL COMMENT '用户名',
          `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
          PRIMARY KEY (`user_id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
      insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);
    5. 创建组件
      创建 BookController
      @Controller
      public class BookController {
          @Autowired
          private BookService bookService;
      public void buyBook(Integer bookId, Integer userId){
          bookService.buyBook(bookId, userId);
          }
      }
      创建接口 BookService
      public interface BookService {
          void buyBook(Integer bookId, Integer userId);
      }
      创建实现类 BookServiceImpl
      @Service
      public class BookServiceImpl implements BookService {
          @Autowired
          private BookDao bookDao;
          @Override
          public void buyBook(Integer bookId, Integer userId) {
          //查询图书的价格
              Integer price = bookDao.getPriceByBookId(bookId);
              //更新图书的库存
              bookDao.updateStock(bookId);
              //更新用户的余额
              bookDao.updateBalance(userId, price);
          }
      }
      创建接口 BookDao
      public interface BookDao {
          Integer getPriceByBookId(Integer bookId);
          void updateStock(Integer bookId);
          void updateBalance(Integer userId, Integer price);
      }

      创建实现类BookDaoImpl

      @Repository
      public class BookDaoImpl implements BookDao {
          @Autowired
          private JdbcTemplate jdbcTemplate;
      @Override
      public Integer getPriceByBookId(Integer bookId) {
          String sql = "select price from t_book where book_id = ?";
          return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
      }
      @Override
      public void updateStock(Integer bookId) {
          String sql = "update t_book set stock = stock - 1 where book_id = ?";
          jdbcTemplate.update(sql, bookId);
      }
      @Override
      public void updateBalance(Integer userId, Integer price) {
          String sql = "update t_user set balance = balance - ? where user_id =?";
          jdbcTemplate.update(sql, price, userId);
          }
      }

     加入事务

    1. 添加事务配置
      Spring 的配置文件中添加配置:
      
      
      
          
      
      
      注意:导入的名称空间需要 tx 结尾的那个。 【尚硅谷】SSM框架之SSM学习笔记_第23张图片
    2. 添加事务注解
      因为 service 层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在 service 层处理
      BookServiceImpl buybook() 添加注解 @Transactional
    3. 观察结果
      由于使用了 Spring 的声明式事务,更新库存和更新余额都没有执行

    @Transactional注解标识的位置  

    @Transactional 标识在方法上,咋只会影响该方法
    @Transactional 标识的类上,咋会影响类中所有的方法

    事务属性:只读

    1. 介绍

      对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这 样数据库就能够针对查询操作来进行优化。
    2. 使用方式

      @Transactional(readOnly = true)
      public void buyBook(Integer bookId, Integer userId) {
          //查询图书的价格
          Integer price = bookDao.getPriceByBookId(bookId);
          //更新图书的库存
          bookDao.updateStock(bookId);
          //更新用户的余额
          bookDao.updateBalance(userId, price);
          //System.out.println(1/0);
      }
    3. 注意
      对增删改操作设置只读会抛出下面异常:
      Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

    事务属性:超时

    1. 介绍
      事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是 Java 程序或 MySQL 数据库或网络连接等等)。
      此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。
      概括来说就是一句话:超时回滚,释放资源。
    2. 使用方式
      @Transactional(timeout = 3)
      public void buyBook(Integer bookId, Integer userId) {
      try {
          TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
          //查询图书的价格
          Integer price = bookDao.getPriceByBookId(bookId);
          //更新图书的库存
          bookDao.updateStock(bookId);
          //更新用户的余额
          bookDao.updateBalance(userId, price);
          //System.out.println(1/0);
      }
    3. 观察结果
      执行过程中抛出异常:
      org.springframework.transaction. TransactionTimedOutException : Transaction timed out:
      deadline was Fri Jun 04 16:25:39 CST 2022

    事务属性:回滚策略

    1. 介绍
      声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

      可以通过@Transactional中相关属性设置回滚策略

      rollbackFor属性:需要设置一个Class类型的对象

      rollbackForClassName属性:需要设置一个字符串类型的全类名

      noRollbackFor属性:需要设置一个Class类型的对象

      rollbackFor属性:需要设置一个字符串类型的全类名

    2. 使用方式
      @Transactional(noRollbackFor = ArithmeticException.class)
      //@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
      public void buyBook(Integer bookId, Integer userId) {
          //查询图书的价格
          Integer price = bookDao.getPriceByBookId(bookId);
          //更新图书的库存
          bookDao.updateStock(bookId);
          //更新用户的余额
          bookDao.updateBalance(userId, price);
          System.out.println(1/0);
      }
    3. 观察结果
      虽然购买图书功能中出现了数学运算异常( ArithmeticException ),但是我们设置的回滚策略是,当出现 ArithmeticException 不发生回滚,因此购买图书的操作正常执行

     事务属性:事务隔离级别

    1. 介绍
      数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事
      务与其他事务隔离的程度称为隔离级别。 SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同
      的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
      隔离级别一共有四种:
      读未提交: READ UNCOMMITTED
      允许 Transaction01 读取 Transaction02 未提交的修改。
      读已提交: READ COMMITTED
      要求 Transaction01 只能读取 Transaction02 已提交的修改。
      可重复读: REPEATABLE READ
      确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。
      串行化: SERIALIZABLE
      确保 Transaction01 可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
      各个隔离级别解决并发问题的能力见下表:
      【尚硅谷】SSM框架之SSM学习笔记_第24张图片

    2. 使用方式
      @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
      @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
      @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
      @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
      @Transactional(isolation = Isolation.SERIALIZABLE)//串行化

     事务属性:事务传播行为

    1. 介绍
      当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中
      运行,也可能开启一个新事务,并在自己的事务中运行。
    2. 测试

      创建接口CheckoutService

      public interface CheckoutService {
          void checkout(Integer[] bookIds, Integer userId);
      }
      创建实现类 CheckoutServiceImpl
      @Service
      public class CheckoutServiceImpl implements CheckoutService {
          @Autowired
          private BookService bookService;
          @Override
          @Transactional
          //一次购买多本图书
          public void checkout(Integer[] bookIds, Integer userId) {
          for (Integer bookId : bookIds) {
                  bookService.buyBook(bookId, userId);
              }
          }
      }
      BookController 中添加方法:
      @Autowired
      private CheckoutService checkoutService;
      public void checkout(Integer[] bookIds, Integer userId){
          checkoutService.checkout(bookIds, userId);
      }
      在数据库中将用户的余额修改为 100
    3. 观察结果
      可以通过 @Transactional 中的 propagation 属性设置事务传播行为
      修改 BookServiceImpl buyBook() 上,注解 @Transactional propagation 属性
      @Transactional(propagation = Propagation.REQUIRED) ,默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法 buyBook() checkout() 中被调用, checkout() 上有事务注解,因此在此事务中执行。所购买的两本图书的价格为 80 50 ,而用户的余额为 100 ,因此在购买第二本图书时余额不足失败,导致整个 checkout() 回滚,即只要有一本书买不了,就都买不了
      @Transactional(propagation = Propagation.REQUIRES_NEW) ,表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在 buyBook() 的事务中执行,因此第一本图 书购买成功,事务结束,第二本图书购买失败,只在第二次的 buyBook() 中回滚,购买第一本图书不受 影响,即能买几本就买几本

    肝了一夜 给个收藏鼓励鼓励!!加油!!2023.2.8   6:39:25

你可能感兴趣的:(学习笔记,学习,mybatis,java,spring,1024程序员节)