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

准备工作

  • IDEA
  • Maven
  • Mysql
  • sqlyog
  • mybatis

创建Maven工程

  1. 创建项目命名SSM

  2. 创建模块命名为MyBatis_HelloWorld

  3. 打包方式设置为jar

    <packaging>jar</packaging>
    
  4. 引入依赖

<dependencies>
	<!-- Mybatis核心 -->
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.7</version>
	</dependency>
	<!-- junit测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	<!-- MySQL驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.3</version>
        <!--因为我的maver库李MySQL版本为5.1.3所以我的版本号为5.1.3 根据自身版本修改-->
		</dependency>
</dependencies>
    ```


#### [创建数据库](https://so.csdn.net/so/search?q=%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93&spm=1001.2101.3001.7020)

打开sqlyog创建数据库ssm并创建表t\_user

```sql
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目录中

  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

    <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"        "http://MyBatis.org/dtd/MyBatis-3-config.dtd"><configuration>    <!--    environments:设置多个连接数据库的环境    属性:	    default:设置默认使用的环境的id    -->    <environments default="development">        <!--        environment:设置具体的连接数据库的环境信息        属性:	        id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境        -->        <environment id="development">            <!--            transactionManager:设置事务管理方式            属性:	            type:设置事务管理方式,type="JDBC|MANAGED"	            type="JDBC":设置当前环境的事务管理都必须手动处理	            type="MANAGED":设置事务被管理,例如spring中的AOP            -->            <transactionManager type="JDBC"/>            <!--            dataSource:设置数据源            属性:	            type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"	            type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建	            type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建	            type="JNDI":调用上下文中的数据源            -->            <dataSource type="POOLED">                <!--设置驱动类的全类名-->                <property name="driver" value="com.mysql.seven.jdbc.Driver"/>                <!--设置连接数据库的连接地址-->                <property name="url" value="jdbc:mysql://localhost:13306"/>                <!--设置连接数据库的用户名-->                <property name="username" value="root"/>                <!--设置连接数据库的密码-->                <property name="password" value="XXXXXXX"/>            </dataSource>        </environment>    </environments>    <!--引入映射文件-->    <mappers>         <mapper resource="UserMapper.xml"/>        <!--        以包为单位,将包下所有的映射文件引入核心配置文件        注意:			1. 此方式必须保证mapper接口和mapper映射文件必须在相同的包下			2. mapper接口要和mapper映射文件的名字一致        -->         </mappers></configuration>
    

创建mapper接口

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

public interface UserMapper {     int insertUser();}

创建MyBatis的映射文件

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

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.seven.mybatis.mapper.UserMapper">     <!-- mapper接口要和映射文件保持两个一致       1.mapper接口的全类名和映射文件的namespace一致       2.mapper接口的方法名要和映射文件的sql的id保持一致       -->    <!--    int insertUser();-->    <insert id="insertUser">        insert  into t_user values (null,'admin','123456',23,'男','2289938711@qq.com')    </insert> </mapper>

创建测试类

如果报错的话就是没有把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日志 -->        <dependency>            <groupId>log4j</groupId>            <artifactId>log4j</artifactId>            <version>1.2.17</version>        </dependency>    </dependencies>
    
  •  在resources里创建log4j.xml

    <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">        <param name="Encoding" value="UTF-8" />        <layout class="org.apache.log4j.PatternLayout">            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />        </layout>    </appender>    <logger name="java.sql">        <level value="debug" />    </logger>    <logger name="org.apache.ibatis">        <level value="info" />    </logger>    <root>        <level value="debug" />        <appender-ref ref="STDOUT" />    </root></log4j:configuration>
    

封装获取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;    }}

测试修改用户

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

测试删除用户

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

测试查询用户

  • 查询单行信息
  1.  <!--    User getUserById();-->    <!--        resultType:设置结果类型,即查询到的数据要转换成的Java类型        resultMap:自定义映射,处理多对一或一对多的映射关系    -->    <select id="getUserById" resultType="com.seven.mybatis.pojo.User">    select * from t_user where id= 1    </select>
    
  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.  <!--     List<User> getAllUser();   -->    <select id="getAllUser" resultType="com.seven.mybatis.pojo.User">        select * from t_user    </select>
    
  2.   public void getAllUser(){        SqlSession session = SqlSessionUtil.getSqlSession();        UserMapper mapper = session.getMapper(UserMapper.class);        List<User> allUser = mapper.getAllUser();        allUser.forEach(System.out::println);        session.close();    }
    

MyBatis核心配置文件

  • typeAliases标签
<typeAliases>		<!--        typeAlias:设置某个具体的类型的别名        属性:        type:需要设置别名的类型的全类名        alias:设置此类型的别名,且别名不区分大小写。若不设置此属性,该类型拥有默认的别名,即类名        -->		<!--<typeAlias type="com.seven.mybatis.bean.User"></typeAlias>-->		<!--<typeAlias type="com.seven.mybatis.bean.User" alias="user">        </typeAlias>-->		<!--以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写-->		<package name="com.seven.mybatis.bean"/>	</typeAliases>
  • environments标签
    <environments default="development">        <!--        environment:设置具体的连接数据库的环境信息        属性:	        id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境        -->        <environment id="development">
  • transactionManager标签
 <!--            transactionManager:设置事务管理方式            属性:	            type:设置事务管理方式,type="JDBC|MANAGED"	            type="JDBC":设置当前环境的事务管理都必须手动处理	            type="MANAGED":设置事务被管理,例如spring中的AOP            -->            <transactionManager type="JDBC"/>
  • properties标签
<properties  resource="jdbc.properties"></properties>             <!--  引用配置文件  -->
  • dateSource标签
<!--            dataSource:设置数据源            属性:	            type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"	            type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建	            type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建	            type="JNDI":调用上下文中的数据源            -->            <dataSource type="POOLED">
  • property标签
<!--设置驱动类的全类名-->                <property name="driver" value="${jdbc.driver}"/>                <!--设置连接数据库的连接地址-->                <property name="url" value="${jdbc.url}"/>                <!--设置连接数据库的用户名-->                <property name="username" value="${jdbc.uname}"/>                <!--设置连接数据库的密码-->                <property name="password" value="${jdbc.password}"/>
  • mapper标签
<mappers>        <!--       以具体的xml为单位引入核心配置文件        -->        <!--    <mapper resource="com.seven.mybatis.mapper.UserMapper.xml"/>-->        <!--        以包为单位,将包下所有的映射文件引入核心配置文件        注意:			1. 此方式必须保证mapper接口和mapper映射文件必须在相同的包下			2. mapper接口要和mapper映射文件的名字一致        -->        <package name="com.seven.mybatis.mapper"></package>     </mappers>

不好意思!开学比较忙马上更新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);
    
      <!--    User  getUserById(@Param("id") Integer id); -->         <select id="getUserById" resultType="User">            select * from t_user where id = #{id}        </select>
    
  • 查询所有t_user表的数据

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

    /**     * 用map集合获取数据     * @param id     * @return     * 以字段名为键 以字段的值为值 如果某一字段值为null 则不会放到map集合中     */    Map<String,Object> getAllUserByMap(@Param("id") Integer id);
    
     <!--    Map<String,Object> getAllUserByMap(@Param("id") Integer id);    -->        <select id="getAllUserByMap" resultType="map">            select * from t_user where id =#{id}        </select>
    
  • 利用map集合获取全部数据

     /**     * 用map集合获取数据mapKey为键 以获取到的数据为值     * @return     */     @MapKey("id")    Map<String,Object> getAllUserByMaps();
    
     <!--    Map<String,Object> getAllUserByMaps();      -->        <select id="getAllUserByMaps" resultType="map">            select  * from  t_user        </select>
    

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


特殊SQL的执行

模糊查询

  •   /*** 测试模糊查询* @return*/  List<User> testMohu(@Param("mohu") String mohu);
    
  • <!--List<User> testMohu(@Param("mohu") String mohu);-->  <select id="testMohu" resultType="User"> <!-- 利用字符串拼接模糊查询 用${}拼接 --> <!--select * from t_user where username like '%${mohu}%'--> <!-- 利用concat函数字符串拼接  麻烦 --> <!--select * from t_user where username like concat('%',#{mohu},'%')--> <!-- 用的最多的方式  建议使用    --> select * from t_user where username like "%"#{mohu}"%" </select> <!-- 都会最好 -->
    

批量删除

  •   /*** 批量删除 * @param ids * @return */ int deleteMore(@Param("ids") String ids);
    
  • <!--int deleteMore(@Param("ids") String ids);-->  <delete id="deleteMore">             <!-- 只能使用${} 用#{}会报错 以后可用foreach 解决 -->    delete from t_user where id in (${ids})</delete>
    

动态设置表名

  •   /*** 动态设置表名,查询所有的用户信息 * @param tableName * @return */  List<User> getAllUser(@Param("tableName") String tableName);
    
  • <!--List<User> getAllUser(@Param("tableName") String tableName);--> <!-- 依然是#{}和${}的问题 --><select id="getAllUser" resultType="User">     select * from ${tableName} </select>
    

获取自增的主键

  •   /*** 添加用户信息 * @param user * @return */  int insertUser(User user);
    
  • <!--int insertUser(User user);--> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> <!-- useGeneratedKeys:设置使用自增的主键keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中-->      insert into t_user values(null,#{username},#{password},#{age},#{sex}) </insert>
    

自定义映射resultMap

 resultMup处理字段和属性

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

<!--resultMap:设置自定义映射 属性: id:表示自定义映射的唯一标识 type:查询的数据要映射的实体类的类型 子标签: id:设置主键的映射关系 result:设置普通字段的映射关系association:设置多对一的映射关系(处理实体类类型的属性)collection:设置一对多的映射关系 属性: property:设置映射关系中实体类中的属性名 column:设置映射关系中表中的字段名 --> <resultMap id="userMap" type="User"> <id property="id" column="id"></id> <result property="userName" column="user_name"></result> <result property="password" column="password"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> </resultMap> <!--List<User> testMohu(@Param("mohu") String mohu);--> <select id="testMohu" resultMap="userMap"> <!--select * from t_user where username like '%${mohu}%'--> select id,user_name,password,age,sex from t_user where user_name like concat('%',#{mohu},'%')  </select>

若字段名和实体类中的属性名不一致, 但是字段名符合数据库的规则(使用_) ,实体类中的

属性

名符合 Java 的规则(使用驼峰)

此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系

a> 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致

b> 可以在 MyBatis 的核心配置文件中设置一个全局配置信息 mapUnderscoreToCamelCase ,可

以在查询表中数据时,自动将 _ 类型的字段名转换为驼峰

例如:字段名 user_name ,设置了 mapUnderscoreToCamelCase ,此时字段名就会转换为

userName

多对一映射处理

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

  • 级联方式处理映射关系

    <resultMap id="empDeptMap" type="Emp"> <id column="eid" property="eid"></id> <result column="ename" property="ename"></result> <result column="age" property="age"></result> <result column="sex" property="sex"></result> <result column="did" property="dept.did"></result> <result column="dname" property="dept.dname"></result> </resultMap>  <!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->  <select id="getEmpAndDeptByEid" resultMap="empDeptMap">  select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}  </select>
    
  • 使用association处理映射关系

    <resultMap id="empDeptMap" type="Emp">      <id column="eid" property="eid"></id>     <result column="ename" property="ename"></result>     <result column="age" property="age"></result>     <result column="sex" property="sex"></result>     <!--                association:处理多对一的映射关系(处理实体类的属性)        property:处理需要处理映射关系的属性的属性名        javaType:设置需要处理的属性的类型     -->    <association property="dept" javaType="Dept">         <id column="did" property="did"></id>         <result column="dname" property="dname"></result>    </association> </resultMap>  <!--Emp getEmpAndDeptByEid(@Param("eid") int eid);--> <select id="getEmpAndDeptByEid" resultMap="empDeptMap">  select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid} </select>
    

分步查询

  1. 查询员工信息

    /*** 通过分步查询查询员工信息 * @param eid * @return */  Emp getEmpByStep(@Param("eid") int eid);
    
    <resultMap id="empDeptStepMap" type="Emp"> <id column="eid" property="eid"></id> <result column="ename" property="ename"></result> <result column="age" property="age"></result> <result column="sex" property="sex"></result> <!--select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId) column:将sql以及查询结果中的某个字段设置为分步查询的条件 -->  <association property="dept"select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did"> </association> </resultMap> <!--Emp getEmpByStep(@Param("eid") int eid);--> <select id="getEmpByStep" resultMap="empDeptStepMap"> select * from t_emp where eid = #{eid} </select>
    
  2. 根据员工所对应的部门id查询部门信息

    /*** 分步查询的第二步: 根据员工所对应的did查询部门信息 * @param did * @return */  Dept getEmpDeptByStep(@Param("did") int did);
    
    <!--Dept getEmpDeptByStep(@Param("did") int did);-->  <select id="getEmpDeptByStep" resultType="Dept">  select * from t_dept where did = #{did}  </select>
    

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

  • 延迟加载

       <settings>        <!--将下划线映射为驼峰-->        <setting name="mapUnderscoreToCamelCase" value="true"/>        <!--开启延迟加载-->        <setting name="lazyLoadingEnabled" value="true"/>        <!--按需加载-->        <setting name="aggressiveLazyLoading" value="false"/>    </settings>
    

分步查询的优点:可以实现延迟加载

但是必须在 核心配置文件中 设置全局配置信息:

lazyLoadingEnabled :延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoading :当开启时,任何方法的调用都会加载该对象的所有属性。

否则,每个属性会按需加载此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql 。

此时可通过 association 和collection中的 fetchType 属性设置当前的分步查询是否使用延迟加载, fetchType=“lazy( 延迟加载)|eager( 立即加载 )”

动态SQL

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

if标签

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

<!--List<Emp> getEmpListByCondition(Emp emp);--> <select id="getEmpListByMoreTJ" resultType="Emp"> <!-- 1=1是因为如果下方条件都不满足的情况下where会报错   还有其他处理方式在下面 -->select * from t_emp where 1=1 <if test="ename != '' and ename != null"> and ename = #{ename} </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="sex != '' and sex != null"> and sex = #{sex} </if> </select>

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


where标签

  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
  • 注意:where标签不能去掉条件最后多余的and
 <select id="getEmpByConditionTwo" resultType="Emp">        select * from t_emp        <where>            <if test="empName != null and empName != ''">                emp_name = #{empName}            </if>            <if test="age != null and age != ''">                <!--即使上面的条件不成立前面的and也会自动消除-->                and age = #{age}            </if>            <if test="gender != null and gender != ''">                and gender = #{gender}            </if>        </where>    </select>

trim标签

  • prefix、suffix:在标签中内容前面或后面添加指定内容
  • prefixOverrides、suffixOverrides:在标签中内容前面或后面去掉指定内容
 <select id="getEmpByCondition" resultType="Emp">        select * from t_emp        <!-- prefix:在内容的前面添加 where 。  suffixOverrides:在内容的后面去掉 and -->        <trim prefix="where" suffixOverrides="and">            <if test="empName != null and empName != ''">                emp_name = #{empName} and            </if>            <if test="age != null and age != ''">                age = #{age} and            </if>            <if test="gender != null and gender != ''">                gender = #{gender}            </if>        </trim>    </select>

choose、when、otherwise 标签

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

<select id="getEmpByChoose" resultType="Emp">        select * from t_emp        <where>            <choose>                <when test="empName != null and empName != ''">                    emp_name = #{empName}                </when>                <when test="age != null and age != ''">                    age = #{age}                </when>                <when test="gender != null and gender != ''">                    gender = #{gender}                </when>            </choose>        </where>    </select>

foreach 标签

循环添加

<insert id="insertMoreEmp">        insert into t_emp values        <!--             collection:设置要循环的数组或集合            item:用一个字符串表示数组或集合中的每一个数据            separator:设置每次循环的数据之间的分隔符            open:循环的所有内容以什么开始            close:循环的所有内容以什么结束        -->        <foreach collection="emps" item="emp" separator=",">            (null,#{emp.empName},#{emp.age},#{emp.gender},null)        </foreach>    </insert>

循环删除

<!--  两种删除方式--> <delete id="deleteMoreEmp">        <!--delete from t_emp where emp_id in        <!--  第一种利用mysql的in函数进行批量删除   -->        <foreach collection="empIds" item="empId" separator="," open="(" close=")">            #{empId}        </foreach>-->          <!--  第二种 利用 where 和 or 进行批量删除         -->        delete from t_emp where        <foreach collection="empIds" item="empId" separator="or">            emp_id = #{empId}        </foreach>    </delete>

sql标签

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

<sql id="empColumns">        emp_id,emp_name,age,gender,dept_id    </sql> <!--   用来引用sql 标签中的字段     --><include refid="empColumns"></include> <!-- 例如 --> <select id="getEmpByCondition" resultType="Emp">        select <include refid="empColumns"></include> from t_emp        <trim prefix="where" suffixOverrides="and">            <if test="empName != null and empName != ''">                emp_name = #{empName} and            </if>            <if test="age != null and age != ''">                age = #{age} and            </if>            <if test="gender != null and gender != ''">                gender = #{gender}            </if>        </trim>    </select>

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. 添加依赖

    <!-- Mybatis EHCache整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <!-- slf4j日志门面的一个具体实现 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
    
  2. 各jar包功能

  3. 创建EHCache的配置文件ehcache.xml

    <?xml version="1.0" encoding="utf-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\atguigu\ehcache"/> <defaultCache maxElementsInMemory="1000"maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true"timeToIdleSeconds="120" timeToLiveSeconds="120"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU">  </defaultCache> </ehcache>
    
  4. 设置二级缓存的类型

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
  5. 加入logback日志

    <?xmlversion="1.0"encoding="UTF-8"?><configurationdebug="true">  <!--指定日志输出的位置-->  <appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">    <encoder>      <!--日志输出的格式-->      <!--按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行-->      <pattern>[%d{HH:mm:ss.SSS}][%-5level][%thread][%logger][%msg]%n</pattern>    </encoder>  </appender><!--设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR-->  <!--指定任何一个日志级别都只打印当前级别和后面级别的日志。-->  <rootlevel="DEBUG">    <!--指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender-->    <appender-refref="STDOUT"/>  </root><!--根据特殊需求指定局部日志级别-->  <loggername="com.atguigu.crowd.mapper"level="DEBUG"/></configuration>
    
  6. EHCache配置文件说明

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

MyBatis****的逆向工程

正向工程:先创建 Java 实体类,由框架负责根据实体类生成数据库表。 Hibernate 是支持正向工

程的。

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:

  • Java实体类
  • Mapper接口
  • Mapper映射文件

创建逆向工程的步骤

  1. 添加依赖和插件

     <!-- 依赖MyBatis核心包 -->    <dependencies>        <dependency>            <groupId>org.mybatis</groupId>            <artifactId>mybatis</artifactId>            <version>3.5.7</version>        </dependency>        <!-- junit测试 -->        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.12</version>            <scope>test</scope>        </dependency>         <!-- log4j日志 -->        <dependency>            <groupId>log4j</groupId>            <artifactId>log4j</artifactId>            <version>1.2.17</version>        </dependency>         <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.16</version>        </dependency>        <dependency>            <groupId>com.github.pagehelper</groupId>            <artifactId>pagehelper</artifactId>            <version>5.2.0</version>        </dependency>    </dependencies>     <!-- 控制Maven在构建过程中相关配置 -->    <build>         <!-- 构建过程中用到的插件 -->        <plugins>             <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->            <plugin>                <groupId>org.mybatis.generator</groupId>                <artifactId>mybatis-generator-maven-plugin</artifactId>                <version>1.3.0</version>                 <!-- 插件的依赖 -->                <dependencies>                     <!-- 逆向工程的核心依赖 -->                    <dependency>                        <groupId>org.mybatis.generator</groupId>                        <artifactId>mybatis-generator-core</artifactId>                        <version>1.3.2</version>                    </dependency>                     <!-- MySQL驱动 -->                    <dependency>                        <groupId>mysql</groupId>                        <artifactId>mysql-connector-java</artifactId>                        <version>8.0.16</version>                    </dependency>                </dependencies>            </plugin>        </plugins>    </build>
    
  2. 创建MyBatis的核心配置文件

  3. 创建逆向工程的配置                                                                                                                                          文件****文件名必须是:generatorConfig.xml

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfiguration        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration>    <!--            targetRuntime: 执行生成的逆向工程的版本                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)                    MyBatis3: 生成带条件的CRUD(奢华尊享版)     -->    <context id="DB2Tables" targetRuntime="MyBatis3">        <!-- 数据库的连接信息 -->        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"                        connectionURL="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"                        userId="root"                        passWord="">                        <!--  此处修改为password  -->         </jdbcConnection>        <!-- javaBean的生成策略-->        <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java">            <property name="enableSubPackages" value="true" />            <property name="trimStrings" value="true" />        </javaModelGenerator>        <!-- SQL映射文件的生成策略 -->        <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"  targetProject=".\src\main\resources">            <property name="enableSubPackages" value="true" />        </sqlMapGenerator>        <!-- Mapper接口的生成策略 -->        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper"  targetProject=".\src\main\java">            <property name="enableSubPackages" value="true" />        </javaClientGenerator>        <!-- 逆向分析的表 -->        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->        <!-- domainObjectName属性指定生成出来的实体类的类名 -->        <table tableName="t_emp" domainObjectName="Emp"/>        <table tableName="t_dept" domainObjectName="Dept"/>    </context></generatorConfiguration>
    
  4. 执行 MBG 插件的generate目标

  5. 效果图

分页插件

分页插件的使用步骤

  1. 添加依赖

    dependency>  <groupId>com.github.pagehelper</groupId>  <artifactId>pagehelper</artifactId>  <version>5.2.0</version></dependency>
    
  2. 配置分页插件

    <plugins><!--      在MyBatis的核心配置文件中配置插件         -->  <!--设置分页插件-->  <plugininterceptor="com.github.pagehelper.PageInterceptor"></plugin></plugins>
    

分页插件的使用

  • 在查询功能之前使用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****思想

IOC:Inversion 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的主要实现类

    | 类型名

    |

    简介

    ClassPathXmlApplicationContext

    |

    通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象

    | |

    FileSystemXmlApplicationContext

    |

    通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象

    | |

    ConfigurableApplicationContext

    |

    ApplicationContext 的子接口,包含一些扩展方法

    refresh() 和 close() ,让 ApplicationContext 具有启动、

    关闭和刷新上下文的能力。

    | |

    WebApplicationContext

    |

    专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对

    象,并将对象引入存入 ServletContext 域中。

    |

     基于XML管理****bean

    实验一:入门案例

    1. 创建Maven Module

    2. 引入依赖

      <dependencies> <!--基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.3.1</version>  </dependency>  <!--junit测试-->  <dependency>    <groupId>junit</groupId>    <artifactId>junit</artifactId>   <version>4.12</version>    <scope>test</scope>  </dependency></dependencies>
      

    3. 创建类HelloWorld

      public class HelloWorld {     public void sayHello(){        System.out.println("hello,spring");    } }
      
    4. 创建Spring的配置文件

    5. 在Spring的配置文件中配置bean

      <!--  配置HelloWorld所对应的bean,即将HelloWorld的对象交给Spring的IOC容器管理  通过bean标签配置IOC容器所管理的bean  属性:    id:设置bean的唯一标识    class:设置bean所对应类型的全类名--><bean id="helloworld"class="com.seven.spring.bean.HelloWorld"></bean>
      
    6. 创建测试类测试

          @Test    public void test(){        //获取IOC容器        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");        //获取IOC容器中的bean        HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");        helloworld.sayHello();    } 
      
    7. 思路

    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 容器中一共配置了两个:

      <bean id="helloworldOne"class="com.atguigu.spring.bean.HelloWorld"></bean><bean id="helloworldTwo"class="com.atguigu.spring.bean.HelloWorld"></bean>
      

      根据类型获取时会抛出异常:

      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时为属性赋值

      <!--        scope :设置bean的作用域        scope = "prototype/singleton"        prototype:(多例) 表示获取该bean所对应的对象都是同一个        singleton:(单例) 表示获取该bean所对应的对象都不是同一个        -->    <bean id="student"  class="com.seven.spring.pojo.Student"         scope="singleton">        <property name="name" value="张三"></property>        <property name="age" value="11"></property>    </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类中添加有参构造**

    ```java
    public Student(Integer id,String name,Integer age,String sex){    this.id=id;  this.name=name;  this.age=age;  this.sex=sex;}
    ```

2. **配置bean**

    ```XML
    <bean id="studentTwo"class="com.atguigu.spring.bean.Student">  <!--   constructor-arg标签还有两个属性可以进一步描述构造器参数:    index属性:指定参数所在位置的索引(从0开始)    name属性:指定参数名     -->  <constructor-arg value="1002"></constructor-arg>  <constructor-arg value="李四"></constructor-arg>  <constructor-arg value="33"></constructor-arg>  <constructor-arg value="女"></constructor-arg></bean>
    ```

3. **测试**

    ```java
    @Testpublic 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 这个字母本身,这就是字面
    >
    > 量。所以字面量没有引申含义,就是我们看到的这个数据本身。

    ```XML
    <!--使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量--><property name="name" value="张三"/>
    ```

2. **null****值**

    ```XML
    <property name="name">  <null/></property>
    ```

    > 注意:
    >
    > ```XML
    > <property name="name" value="null"></property>
    > ```
    >
    > 以上写法,为 name 所赋的值是字符串 null

3. **xml****实体**

    ```XML
    <!--小于号在XML文档中用来定义标签的开始,不能随便使用--><!--解决方案一:使用XML实体来代替--><property name="expression" value="a&lt;b"/>
    ```

4. **CDATA节**

    ```XML
    <property name="expression">  <!--解决方案二:使用CDATA节-->  <!--CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据-->    <!--XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析-->    <!--所以CDATA节中写什么符号都随意-->  <value><![CDATA[a<b]]></value></property>
    ```


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

1. **创建班级类Clazz**

    ```java
    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 类中添加以下代码:

    ```java
    private Clazz clazz;public Clazz getClazz() {return clazz;}public void setClazz(Clazz clazz) {this.clazz = clazz;}
    ```

3. **方式一:引用外部已声明的bean**

    ```XML
    <bean id="clazzOne" class="com.atguigu.spring.bean.Clazz"><property name="clazzId" value="1111"></property><property name="clazzName" value="财源滚滚班"></property></bean>
    ```

    为 Student 中的 clazz 属性赋值:

    ```XML
    <bean id="studentFour" class="com.atguigu.spring.bean.Student"><property name="id" value="1004"></property><property name="name" value="赵六"></property><property name="age" value="26"></property><property name="sex" value="女"></property><!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 --><property name="clazz" ref="clazzOne"></property></bean>
    ```

    错误演示:

    ```XML
    <bean id="studentFour" class="com.atguigu.spring.bean.Student"><property name="id" value="1004"></property><property name="name" value="赵六"></property><property name="age" value="26"></property><property name="sex" value="女"></property><property name="clazz" value="clazzOne"></property></bean>
    ```

    > 如果错把 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**

    ```XML
    <bean id="studentFour" class="com.atguigu.spring.bean.Student"><property name="id" value="1004"></property><property name="name" value="赵六"></property><property name="age" value="26"></property><property name="sex" value="女"></property><property name="clazz"><!-- 在一个bean中再声明一个bean就是内部bean --><!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 --><bean id="clazzInner" class="com.atguigu.spring.bean.Clazz"><property name="clazzId" value="2222"></property><property name="clazzName" value="远大前程班"></property></bean></property></bean>
    ```

5. **方式三:级联属性赋值**

    ```XML
    <bean id="studentFour" class="com.atguigu.spring.bean.Student"><property name="id" value="1004"></property><property name="name" value="赵六"></property><property name="age" value="26"></property><property name="sex" value="女"></property><!-- 一定先引用某个bean为属性赋值,才可以使用级联方式更新属性 --><property name="clazz" ref="clazzOne"></property><property name="clazz.clazzId" value="3333"></property><property name="clazz.clazzName" value="最强王者班"></property></bean>
    ```


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

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

1. **修改****Student****类**                                                                                                                在Student类中添加以下代码:

    ```java
    private String[] hobbies;public String[] getHobbies() {return hobbies;}public void setHobbies(String[] hobbies) {this.hobbies = hobbies;}
    ```

2. **配bean**

    ```XML
      <bean id="student"  class="com.seven.spring.pojo.Student"  scope="singleton">        <property name="name" value="张三"></property>        <property name="age" value="11"></property>    </bean>     <bean id="studentFour" class="com.seven.spring.pojo.Student">        <property name="name" value="赵六"></property>        <property name="age" value="26"></property>        <property name="dept" ref="dept"></property>        <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->        <property name="hobbies">            <array>                <value>抽烟</value>                <value>喝酒</value>                <value>烫头</value>            </array>        </property>    </bean>    <bean id="dept" class="com.seven.spring.pojo.Dept">        <property name="className" value="软件212"></property>    </bean>
    ```

3. **测试类**

    ```java
     @Test    public void testScope(){        ApplicationContext ioc  = new ClassPathXmlApplicationContext("spring-scope.xml");        Student bean = ioc.getBean("studentFour",Student.class);// 我重写了tostring方法所以可以直接输出        System.out.println(bean);    }
    ```

4. **效果**![](https://i-blog.csdnimg.cn/blog_migrate/a2a4dba9c59c73bf7d596096bffe61c8.png)

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

1. **加入依赖**

    ```XML
    <!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency>
    ```

2. **创建外部属性文件**

    ![](https://i-blog.csdnimg.cn/blog_migrate/ee1a20011d907c06e8eb4799dde40031.png)

    ```XML
    jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:13306/ssm?serverTimezone=UTCjdbc.username=rootjdbc.password=asd1230.
    ```

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

3. **引入并且配置bean**

    ```XML
    <!--引入属性文件--><context:property-placeholder location="jdbc.properties"></context:property-placeholder> <!--给数据源配置bean--><bean id="dateSource" class="com.alibaba.druid.pool.DruidDataSource">      <property name="driverClassName" value="${jdbc.driver}"></property>      <property name="url" value="${jdbc.url}"></property>      <property name="username" value="${jdbc.username}"></property>      <property name="password" value="${jdbc.password}"></property></bean>
    ```

4. 测试类

    ```java
        @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. 输出结果![](https://i-blog.csdnimg.cn/blog_migrate/337393f720748c5b34d8b23e9f90cdbe.png)

####  **实验九:bean的作用域**

1. **概念**                                                                                                                               在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

    |
    **取值**

     | 含义 |

    **创建对象的时机**

     |
    | --- | --- | --- |
    |

    singleton (默认)

     |

    在 IOC 容器中,这个 bean 的对象始终为单实例

     |

    IOC 容器初始化时

     |
    |

    prototype

     |

    这个 bean 在 IOC 容器中有多个实例

     |

    获取 bean 时

     |

    如果是在 WebApplicationContext 环境下还会有另外两个作用域(但不常用):

    |
    **取值**

     | 含义 |
    | --- | --- |
    |

    request

     |

    在一个请求范围内有效

     |
    |

    session

     |

    在一个会话范围内有效

     |

2. **创建类User**

    ```java
    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;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", age=" + age +'}';}}
    ```

3. **配置** **bean**

    ```XML
    <!--   scope :设置bean的作用域   scope = "prototype/singleton"   singleton:(单例) 表示获取该bean所对应的对象都是同一个   prototype:(多例) 表示获取该bean所对应的对象都不是同一个    --><bean id="student"  class="com.seven.spring.pojo.Student"  scope="singleton">   <property name="name" value="张三"></property>   <property name="age" value="11"></property></bean>
    ```

4. **测试**

    ```java
      @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);    }
    ```

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

    ![](https://i-blog.csdnimg.cn/blog_migrate/9da3d4a58ae1d112d4f79f684c722763.png)

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

    ![](https://i-blog.csdnimg.cn/blog_migrate/23fb1dfd3fdda1626813ab75835a4f9b.png)

    ###

    ###


###

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、销毁");}@Overridepublic String toString() {return "User{" +"id=" + id +", username =='" + username + '\'' +", password =='" + password + '\'' +", age=" + age +'}';}}
    

    注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法

  3. 配置 bean

    <!-- 使用init-method属性指定初始化方法 --><!-- 使用destroy-method属性指定销毁方法 --><bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod"destroy-method="destroyMethod"><property name="id" value="1001"></property><property name="username" value="admin"></property><property name="password" value="123456"></property><property name="age" value="23"></property></bean>
    
  4. 测试

    @Testpublic void testLife(){ClassPathXmlApplicationContext ac = newClassPathXmlApplicationContext("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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {System.out.println("☆☆☆" + beanName + " = " + bean);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {System.out.println("★★★" + beanName + " = " + bean);return bean;}}
    

    在 IOC 容器中配置后置处理器:

    <!-- bean的后置处理器要放入IOC容器才能生效 --><bean id="myBeanProcessor" class="com.atguigu.spring.process.MyBeanProcessor"/>
    

**实验十三:**FactoryBean

  1. 简介

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

    将来我们整合 Mybatis 时, Spring 就是通过 FactoryBean 机制来帮我们创建 SqlSessionFactory 对象的。

  2. 创建类****UserFactoryBean

    public class UserFactoryBean implements FactoryBean<User> {@Overridepublic User getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return User.class;}}
    
  3. 配置 bean

    <bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
    
  4. 测试

    @Testpublic 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;}@Overridepublic void saveUser() {userDao.saveUser();}}
    

    创建接口 UserDao

    public interface UserDao {void saveUser();}
    

    创建类 UserDaoImpl 实现接口UserDao

    public class UserDaoImpl implements UserDao {@Overridepublic void saveUser() {System.out.println("保存成功");}}
    
  2. 配置 bean

    使用 bean 标签的 autowire 属性设置自动装配效果

    自动装配方式:byType

    byType :根据类型匹配 IOC 容器中的某个兼容类型的 bean ,为属性自动赋值

    若在 IOC 中,没有任何一个兼容类型的 bean 能够为属性赋值,则该属性不装配,即值为默认值 null

    若在 IOC 中,有多个兼容类型的 bean 能够为属性赋值,则抛出异常

    NoUniqueBeanDefinitionException

    <bean id="userController"class="com.atguigu.autowire.xml.controller.UserController" autowire="byType"></bean><bean id="userService"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType"></bean><bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
    

    自动装配方式: byName

    byName :将自动装配的属性的属性名,作为 bean 的 id 在 IOC 容器中匹配相对应的 bean 进行赋值

    <bean id="userController"class="com.atguigu.autowire.xml.controller.UserController" autowire="byName"></bean><bean id="userService"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName"></bean><bean id="userServiceImpl"class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName"></bean><bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean><bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
    
  3. 测试

    @Testpublic 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

<dependencies>  <!--基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包-->  <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.3.1</version>  </dependency><!--junit测试-->  <dependency>    <groupId>junit</groupId>    <artifactId>junit</artifactId>      <version>4.12</version>    <scope>test</scope>  </dependency></dependencies>

4.创建spring配置文件

5.标识组件的常用注解

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

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

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

2. 基于注解的自动装配

@Autowired注解(自动装配)

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

@Controllerpublic class UserController { @Autowiredprivate 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

技术说明

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

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

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

准备工作

  1. 添加依赖

        <dependencies>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-context</artifactId>            <version>5.3.1</version>        </dependency>        <!-- spring-aspects会帮我们传递过来aspectjweaver -->        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-aspects</artifactId>            <version>5.3.1</version>        </dependency>    <!-- junit测试 -->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.12</version>        <scope>test</scope>    </dependency></dependencies>
    
  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容器@Componentpublic 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 的配置文件中配置:

<!--基于注解的AOP的实现:1、将目标对象和切面交给IOC容器管理(注解+扫描)2、开启AspectJ的自动代理,为目标对象自动生成代理3、将切面类通过注解@Aspect标识--><context:component-scan base-package="com.atguigu.aop.annotation"></context:component-scan><aop:aspectj-autoproxy />

各种通知

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

切入点表达式语法

  1. 作用

    2.  语法细节

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

重用切入点表达式

  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. 获取连接点信息

  2. 获取连接点信息可以在通知方法的参数位置设置 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);}
    
  3. 获取目标方法的返回值

    @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);}
    
  4. 获取目标方法的异****常

    @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(较大的数):优先级低

 声明式事务

JdbcTemplate

简介

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

准备工作

  1. 加入依赖

    <dependencies>    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.1</version>    </dependency>    <!-- Spring 持久化层支持jar包 -->    <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个    jar包 -->    <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-orm</artifactId>        <version>5.3.1</version>    </dependency>    <!-- Spring 测试相关 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-test</artifactId>        <version>5.3.1</version>    </dependency>    <!-- junit测试 -->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.12</version>        <scope>test</scope>    </dependency>    <!-- MySQL驱动 -->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.16</version>    </dependency>    <!-- 数据源 -->    <dependency>        <groupId>com.alibaba</groupId>        <artifactId>druid</artifactId>        <version>1.0.31</version>    </dependency></dependencies>
    
  2. 创建 jdbc.properties

    jdbc.user=rootjdbc.password=atguigujdbc.url=jdbc:mysql://localhost:3306/ssmjdbc.driver=com.mysql.cj.jdbc.Driver
    
  3. 配置 Spring 的配置文件

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <!-- 导入外部属性文件 -->    <context:property-placeholder location="classpath:jdbc.properties" />    <!-- 配置数据源 -->    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">        <property name="url" value="${atguigu.url}"/>        <property name="driverClassName" value="${atguigu.driver}"/>        <property name="username" value="${atguigu.username}"/>        <property name="password" value="${atguigu.password}"/>    </bean>    <!-- 配置 JdbcTemplate -->    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">        <!-- 装配数据源 -->        <property name="dataSource" ref="druidDataSource"/>    </bean></beans>
    

    测试

    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. 加入依赖

     <dependencies>    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.3.1</version>    </dependency>    <!-- Spring 持久化层支持jar包 -->    <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个    jar包 -->    <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-orm</artifactId>        <version>5.3.1</version>    </dependency>    <!-- Spring 测试相关 -->    <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-test</artifactId>    <version>5.3.1</version></dependency>        <!-- junit测试 -->        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.12</version>            <scope>test</scope>        </dependency>        <!-- MySQL驱动 -->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.16</version>        </dependency>        <!-- 数据源 -->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid</artifactId>            <version>1.0.31</version>        </dependency>    </dependencies>
    
  2. 创建 jdbc.properties

    jdbc.user=rootjdbc.password=123456jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTCjdbc.driver=com.mysql.cj.jdbc.Driver
    
  3. 配置 Spring 的配置文件

    <!--扫描组件--><context:component-scan base-package="com.atguigu.spring.tx.annotation"></context:component-scan><!-- 导入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置数据源 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!-- 配置 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!-- 装配数据源 --><property name="dataSource" ref="druidDataSource"/></bean>
    
  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 :

    @Controllerpublic 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 :

    @Servicepublic 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:

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

加入事务

  1. 添加事务配置

    在 Spring 的配置文件中添加配置:

    <bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean>    <!--    开启事务的注解驱动    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务    --><!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 --><tx:annotation-driven transaction-manager="transactionManager" />
    

    注意:导入的名称空间需要 tx 结尾的那个。

  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执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

    各个隔离级别解决并发问题的能力见下表:

  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 :

    @Servicepublic 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 中添加方法:

    @Autowiredprivate 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

本文转自 https://blog.csdn.net/m0_60824353/article/details/126626053?ops_request_misc=elastic_search_misc&request_id=f447745b1cf053f40561f5c683e5e49a&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-126626053-null-null.142^v102^pc_search_result_base9&utm_term=ssm%E7%AC%94%E8%AE%B0&spm=1018.2226.3001.4187,如有侵权,请联系删除。