MyBatis&Spring Framrwork

  1. MyBatis

1.1 概述

  • MyBatis是一款优秀的持久层框架,用于简化JDBC开发
  • MyBatis本是Apache的一个开源项目iBatis,2010年这个项目迁移到了google code,并改名为MyBatis。2013年迁移到Github
  • 官网:https://mybatis.net.cn
  • 持久层
  • 负责将数据保存到数据库的那一层代码
  • JavaEE三层架构:表现层、业务层、持久层
  • MyBatis简化JDBC
  • 硬编码:—->配置文件
    • 注册驱动,获取连接
    • SQL语句
  • 操作繁琐:—->自动完成
    • 手动设置参数
    • 手动封装给结果集 *MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作

1.2 MyBatis快速入门

  • 需求:查询user表中所有的数据
  • 创建user表,添加数据
create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
  • 创建模块,导入坐标

            org.mybatis
            mybatis
            3.5.5

            mysql
            mysql-connector-java
            8.0.29

            junit
            junit
            4.11
            test

            org.slf4j
            slf4j-api
            1.7.20

            ch.qos.logback
            logback-classic
            1.2.3

            ch.qos.logback
            logback-core
            1.2.3

  • 编写 MyBatis 核心配置文件 –> 替换连接信息 解决硬编码问题 从 XML 中构建 SqlSessionFactory 在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml,内容如下

  • 编写 SQL 映射文件 –> 统一管理sql语句,解决硬编码问题 在模块的 resources 目录下创建映射配置文件 UserMapper.xml ,内容如下:

        select * from tb_user;

  • 编码
    • 定义pojo类
package com.mark.pojo;

/**
 * @ClassName User
 * @Description TODO
 * @Author Mark
 * @Date 2022/8/29 16:04
 * @Version 1.0
 */
public class User {
    //alt + 拖动鼠标左键 整列编辑
    private Integer id;
    private String username;
    private String password;
    private String gender;
    private String addr;

    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 String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}

+ 加载核心配置文件,获取SqlSessionFactory对象
+ 获取SqlSession对象,执行SQL语句
+ 释放资源
package com.mark;

import com.mark.pojo.User;
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;
import java.util.List;

/**
 * @ClassName MyBatisDemo
 * @Description TODO MyBatis快速入门
 * @Author Mark
 * @Date 2022/8/29 16:13
 * @Version 1.0
 */
public class MyBatisDemo {
    public static void main(String[] args) throws IOException {
        //1.加载Mybatis核心配置文件,获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取SqlSession对象,执行SQL语句
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List users = sqlSession.selectList("test.selectAll");
        System.out.println(users);
        //3.释放资源
        sqlSession.close();
    }
}

  • 在IDEA中配置MySql数据库连接
  • 在IDEA右侧点击Database
  • 左边”+”—>Data Source—>MySQL
  • 输入配置

1.3 Mapper代理开发

  • 目的:
  • 解决原生方式中的硬编码
  • 简化后期执行SQL
  • 步骤
    MyBatis&Spring Framrwork

  • 设置SQL映射文件的namespace属性为Mapper接口全限定名

        select * from tb_user;

  • 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
package com.mark.mapper;

import com.mark.pojo.User;

import java.util.List;

public interface UserMapper {
    List selectAll();
}
  • 编码
    • 通过SqlSession的getMapper方法获取Mapper接口的代理对象
    • 调用对应方法完成sql的执行
package com.mark;

import com.mark.mapper.UserMapper;
import com.mark.pojo.User;
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;
import java.util.List;

/**
 * @ClassName MyBatisDemo
 * @Description TODO Mapper代理开发
 * @Author Mark
 * @Date 2022/8/29 16:13
 * @Version 1.0
 */
public class MyBatisDemo2 {
    public static void main(String[] args) throws IOException {
        //1.加载Mybatis核心配置文件,获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取SqlSession对象,执行SQL语句
        SqlSession sqlSession = sqlSessionFactory.openSession();

//        List users = sqlSession.selectList("test.selectAll");
//        System.out.println(users);

        //⭐2.1获取UserMapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //⭐2.2执行对应方法
        List users = userMapper.selectAll();

        System.out.println(users);
        //3.释放资源
        sqlSession.close();
    }
}

如果Mapper接口名称和SQL映射文件名称相同,在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载


1.4 MyBatis核心配置文件

mybatis-config.xml


在配置标签时应当注意顺序

1.5 配置文件完成增删改查

  • 创建tb_brand表并添加数据
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1);
  • 创建测试类MyBatisTest
  • 创建Brand类
package com.mark.pojo;

/**
 * @ClassName Brand
 * @Description TODO
 * @Author Mark
 * @Date 2022/8/29 18:10
 * @Version 1.0
 */
public class Brand {
    // id 主键
    private Integer id;
    // 品牌名称
    private String brandName;
    // 企业名称
    private String companyName;
    // 排序字段
    private Integer ordered;
    // 描述信息
    private String description;
    // 状态:0:禁用 1:启用
    private Integer status;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

  • 创建BrandMapper接口
package com.mark.mapper;

import com.mark.pojo.Brand;

import java.util.List;

public interface BrandMapper {
    public List selectAll();
}
  • 创建BrandMapper.xmlSQL映射文件

1.5.1 查询所有数据

  • 在BrandMapper接口中定义方法selectAll()
public interface BrandMapper {
    public List selectAll();
}
  • alt+enter生成映射文件中的select标签,并编写查询语句

        select *
        from tb_brand;

  • 编写测试类执行方法查询
public class MyBatisTest {
    @Test
    public void testSelectAll() throws IOException {
        //1.获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取sqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3.获取Mapper接口的代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        //4.执行方法
        List brands = brandMapper.selectAll();
        System.out.println(brands);
        //5.释放资源
        sqlSession.close();
    }
}

结果映射时数据库表的字段名称和实体类的属性名称不一样,则不能自动封装数据,解决方式有两种:

  • 给数据库中字段名称不一样的起别名,让数据库中字段与实体类相同

    select  id, brand_name as brandName, company_name as companyName, ordered, description, status
    from tb_brand;

  • 缺点:每次查询都要定义一次别名
    • sql片段

    id, brand_name as brandName, company_name as companyName, ordered, description, status

    select

    from tb_brand;

  * 缺点:不灵活
  • ⭐resultMap:
  • 定义
  • 在 标签中使用resultMap属性替换resultType属性

    select *
    from tb_brand;

两处细节

  • 参数占位符:
  • {}:会将其替换为?,防止SQL注入

  • ${}:拼sql。会存在sql注入问题 使用时机:
  • 参数传递的时候用#{}
  • 表名或者列名不固定时用${}

    select *
    from tb_brand where id = #{id};

Brand selectById(int id);
//接收参数
int id = 1;
//4.执行方法
Brand brand = brandMapper.selectById(id);
System.out.println(brand);
  • 参数类型:parameterType
  • 特殊字符处理:
  • 转义字符 如
  • CDATA区(大写CD回车即可): <!--[CDATA[ ]]-->

条件查询

  • 多条件查询
  • 散装参数:如果方法中有多个参数,需要使用 @Param("SQL&#x53C2;&#x6570;&#x5360;&#x4F4D;&#x7B26;&#x540D;&#x79F0;") List<brand> selectByCondition(@Param("status")int status,@Param("companyName")String companyName,@Param("brandName")String brandName);</brand>

    select *
    from tb_brand
    where
        status = #{status}
        and company_name like #{companyName}
        and brand_name like #{brandName}
    ;

//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";

//处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";

//4.执行方法
List brands = brandMapper.selectByCondition(status, companyName, brandName);
System.out.println(brands);
  • 对象参数:对象的属性名称要和参数占位符名称一致 List<brand> selectByCondition(Brand brand);</brand>
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";

//处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";

Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);

//4.执行方法
List brands = brandMapper.selectByCondition(brand);
System.out.println(brands);
  • map集合参数 List<brand> selectByCondition(Map map);</brand>
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";

//处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";

Map map =new HashMap();
map.put("status",status);
map.put("companyName",companyName);
map.put("brandName",brandName);

//4.执行方法
List brands = brandMapper.selectByCondition(map);
System.out.println(brands);

1.5.2 添加

添加步骤:

  • 编写接口方法:Mapper接口
  • 参数:除了id之外的所有数据
  • 结果:void void add(Brand brand);
  • 编写SQL语句:SQL映射文件

    insert into tb_brand(brand_name, company_name, ordered, description, status)
    values(#{brandName},#{companyName},#{ordered},#{description},#{status});

  • 执行方法,测试
  • MyBatis事务:
    • openSession():默认开启事务,进行增删改操作后需要使用sqlSession.commit(),手动提交事务
    • openSession(true):可以设置为自动提交事务(关闭事务)
@Test
public void testAdd() throws IOException {
    //接收参数
    int status = 1;
    String companyName = "菠萝手机";
    String brandName = "菠萝";
    String description = "菠萝手机就是牛";
    int ordered = 100;
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);
    //1.获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2.获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //3.获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4.执行方法
    brandMapper.add(brand);

    //提交事务
    //sqlSession.commit();
    //5.释放资源
    sqlSession.close();
}

主键返回:

在数据添加成功后,需要获取插入数据库的主键的值,如订单和订单项

  • 添加订单
  • 添加订单项,订单项中需要设置所属订单的id

    insert into tb_brand(brand_name, company_name, ordered, description, status)
    values(#{brandName},#{companyName},#{ordered},#{description},#{status});

这时调用方法的getId即可取出id值

alter table tablename auto_increment = &#x65B0;&#x6570;&#x5B57;;可以 重新设置自增id开始数字

1.5.3 修改

  • 修改全部字段
  • 编写接口方法
    • 参数:所有数据
    • 结果:void int update(Brand brand);
  • 编写SQL语句:SQL映射文件

    update tb_brand
    set brand_name   = #{brandName},
        company_name = #{companyName},
        ordered      = #{ordered},
        description  = #{description},
        status       = #{status}
    where id = #{id};

  • 执行方法,测试
@Test
public void testupdate() throws IOException {
    //接收参数
    //接收参数
    int status = 1;
    String companyName = "菠萝手机";
    String brandName = "菠萝";
    String description = "菠萝手机,手机中的战斗机";
    int ordered = 200;
    int id = 4;

    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);
    brand.setId(id);

    //1.获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2.获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //3.获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4.执行方法
    int change = brandMapper.update(brand);
    System.out.println(change);
    //5.释放资源
    sqlSession.close();
}
  • 修改动态字段

    update tb_brand

            brand_name = #{brandName},

            company_name = #{companyName},

            ordered = #{ordered},

            description = #{description},

            status = #{status}

    where id = #{id};

1.5.4 删除

  • 删除一个 void deleteById(int id);

    delete from tb_brand where id = #{id};

  • 批量删除
  • 编写接口方法:Mapper数组
    • 参数:id数组
    • 结果:void void deleteByIds(@Param("ids")int[] ids);
  • 编写SQL语句:SQL映射文件

    delete from tb_brand where id
    in

        #{id}

    ;

  • 执行方法,测试
@Test
public void testDeleteByIds() throws IOException {
    int ids[] = {1,3};
    //1.获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2.获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3.获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4.执行方法
    brandMapper.deleteByIds(ids);

    sqlSession.commit();
    //5.释放资源
    sqlSession.close();
}

1.5.5 参数传递

MyBatis接口方法中可以接收各种各样的参数,MyBatis底层对这些数据进行不同的封装处理方式

  • 单个参数
  • POJO类型:可以直接使用,属性名要和参数占位符名称一致,否则要用
  • Map集合:可以直接使用,键名要和参数占位符名称一致 ,否则要用
  • Collection:最终封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg0键名 map.put("collection",collection&#x96C6;&#x5408;); map.put("arg0",collection&#x96C6;&#x5408;);
  • List:最终封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg0键名 map.put("collection",List&#x96C6;&#x5408;); map.put("list",List&#x96C6;&#x5408;); map.put("arg0",List&#x96C6;&#x5408;);
  • Array:最终封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg0键名 map.put("array",&#x6570;&#x7EC4;); map.put("arg0",&#x6570;&#x7EC4;);
  • 其他类型:直接使用,如int id,而且这类参数名称叫什么都无所谓,#{}中写什么都能接收

    select * from tb_user where id = #{xxx};

  • 多个参数 当 有多个参数时,MyBatis自动会将他们封装为Map集合 User select(String username,String password); 底层会这样封装 map.put("arg0",&#x53C2;&#x6570;&#x503C;1); map.put("param1",&#x53C2;&#x6570;&#x503C;1); map.put("param2",&#x53C2;&#x6570;&#x503C;2); map.put("arg1",&#x53C2;&#x6570;&#x503C;2); 如果不写@param注解可以使用arg或者param获取值

    select *
    from tb_user
    where
        username = #{arg0}
        and password = #{arg1}
    ;

而@param注解就是替换默认的arg键名 User select(@param("username")String username,@param("password")String password); map.put("username",&#x53C2;&#x6570;&#x503C;1); map.put("param1",&#x53C2;&#x6570;&#x503C;1); map.put("param2",&#x53C2;&#x6570;&#x503C;2); map.put("password",&#x53C2;&#x6570;&#x503C;2);


    select *
    from tb_user
    where
        username = #{username}
        and password = #{password}
    ;

MyBatis提供了ParamNamePesolver类来进行参数封装

总结:Collection、List、Array、多个参数要加@Param注解修改Map中的键名,并用修改后的名称来获取值,这样可读性更高

1.6 注解完成增删改查

使用注解开发会比配置文件开发更为方便

@Select("select * from tn_user where id = #{id}")
public User selectById(int id);
  • 查询:@Select
  • 添加:@Insert
  • 修改:@Update
  • 删除:@Delete

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

注解开发一般完成比较简单的功能,而配置文件用来完成复杂功能

1.7 动态SQL

SQL语句会随着用户的输入和外部条件的变化而变化,我们称之为动态SQL

MyBatis对动态SQL有强大的支撑:

  • if:条件判断 test:逻辑表达式

    select *
    from tb_brand
    /*where 1 = 1*/

        and status = #{status}

        and company_name like #{companyName}

        and brand_name like #{brandName}

  • choose(when):选择,类似于Java中的switch语句
  • choose标签相当于switch
  • when标签相当于case
  • otherwise标签相当于default

    select *
    from tb_brand
    /*where 1 = 1*/

                status = #{status}

                company_name like #{companyName}

                brand_name like #{companyName}

  1. Spring

2.1 初识Spring

官网:https://spring.io

Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个项目,每个项目完成特定的功能

  • Spring家族
  • Spring Framework:Spring所有的技术都依赖它执行,是底层设计型的框架
  • Spring Boot:可以再Spring简化开发的基础上加速开发
  • Spring Cloud:分布式开发
  • Spring发展史
  • 1997年出现了EJB思想
  • 2004年Rod Johnson写了《J2EE Development without EJB》,Spring1.0问世,使用纯配置开发
  • 2006年Spring2.0 引入了注解功能
  • 2009年Spring3.0 已经演化成了可以不写配置的开发模式
  • 2013年Spring4.0 Spring紧跟JDK版本升级,对个别API进行了些许调整
  • 2017年,Spring到达了Spring5.0版本,已经全面支持JDK1.8

2.2 Spring Framework系统架构

Spring Framework是Spring生态圈最基础的项目,是其他项目的根基,其他所有的项目都在他的基础上运行

MyBatis&Spring Framrwork
– ⭐Core Container:核心容器:对对象进行管理
– AOP:面向切面编程:在不修改源码的情况下,对源码进行增强
– Aspects:AOP思想实现
– Data Access:数据访问
– Data Integration:数据集成:支持Spring技术与其他技术整合使用(包容其他技术)
+ ⭐Transactions:提供了一种开发起来效率非常高的事务控制方案
– Web:Web开发,在学习SpringMVC时进一步探讨
– Test:单元测试与集成测试
* Spring Framework学习路线
– 核心容器Core Container
+ 核心概念(IOC/DI)
+ 容器基本操作
– 数据访问/数据集成Data Access/Integration
+ 整合数据层技术MyBatis
– 面向切面编程AOP、Aspects
+ 核心概念
+ AOP基础操作
+ AOP实用开发
– 事务Transactions
+ 事务实用开发

2.3 核心概念

如今JavaWeb 代码书写现状耦合度偏高

//业务层
public class BookServiceImpl implements BookService{
    private BookDao bookDao = new BookDaoImpl();

    public void save(){
        bookDao.save();
    }
}
//数据层
public class BookDaoImpl implements BookDao{
    piblic void save(){
        System.out.println("book dao save...");
    }
}

当我们想要修改数据层代码

//数据层
public class BookDaoImpl2 implements BookDao{
    piblic void save(){
        System.out.println("book dao save...2");
    }
}

这时业务层也需要修改,然后源码就要重新编译、测试…

解决方案

使用对象时,在程序中不要主动使用new产生对象,转为由 外部提供对象,即 IoC

  • IoC(Inversion of control):控制反转
  • 使用对象时,由主动new产生对象转换为由 外部提供对象,此过程中对象的创建控制权由程序转移到 外部,这种思想称为控制反转
  • Spring技术对IoC思想进行了实现
  • Spring提供了一个容器,称为 IoC容器,用来充当IoC思想中的” 外部
  • Spring框架中,由主动new产生对象转换为由Ioc容器提供对象
//业务层
public class BookServiceImpl implements BookService{
    private BookDao bookDao;

    public void save(){
        bookDao.save();
    }
}
//数据层
public class BookDaoImpl implements BookDao{
    piblic void save(){
        System.out.println("book dao save...");
    }
}

MyBatis&Spring Framrwork
* IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为 Bean
* Service要依赖dao运行,IoC将会绑定两个对象,这种思想称作 DI(Dependency Injection)依赖注入
– 在容器中建立bean和bean之间的依赖关系的整个过程,称为依赖注入
* IoC的目标: 充分解耦
– 使用IoC容器管理bean(IoC)
– 在IoC容器内将有依赖关系的bean进行关系绑定(DI)
* 最终效果:
– 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

2.4 IoC入门案例(XML版)

案例基础:

MyBatis&Spring Framrwork

其中两个实现类分别为

public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();

    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
}
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save...");
    }
}
  • 思路分析
  • IoC管什么?
    • Bean(Service和Dao)
  • 如何将被管理的对象告知(交给)IoC容器?
    • 配置
  • 被管理的对象交给IoC容器,如何获取到IoC容器?
    • 接口
  • IoC容器得到后,如何从容器中获取Bean?
    • 接口方法
  • 使用Spring导入哪些坐标?
    • pom.xml
  • 代码实现
  • pom.xml引入Spring坐标

    org.springframework
    spring-context
    5.2.10.RELEASE

  • 在resource中创建配置文件applicationContext.xml
  • 配置Bean
    • bean表示配置bean
    • id属性表示给bean起名字
    • class属性表示给bean定义类型

  • 获取IOC容器
  • 获取Bean
  • 调用实现类的方法
public class App2 {
    public static void main(String[] args) {
        //获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取Bean
//        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
//        bookDao.save();

        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

//book service save...

//book dao save...

2.5 DI入门案例

  • 思路分析
  • 基于IoC管理bean
  • Service中使用new形式创建的Dao对象是否保留?
  • Service中需要的Dao对象如何进入到Service中?
    • 提供方法
  • Service与Dao间的关系如何描述?
    • 配置
  • 案例实现
  • 删除业务层中使用new的方式创建的dao对象 private BookDao bookDao;
  • 提供对应的set方法
public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
}
  • 配置Service与Dao的关系
    • property标签表示配置当前bean的属性
    • name属性表示配置实现类哪一个具体的属性
    • ref属性表示参照哪一个bean

2.6 bean配置

  • bean基础配置类别 描述 名称 bean 类型 标签 所属 beans标签 功能 定义Spring核心容器管理的对象 格式 <beans> <bean> <bean> </bean> </bean></beans> 属性列表 id:Bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一
    class:bean的类型,即配置bean的全路径类名 范例 <bean id="bookDao" class="com.mark.dao.impl.BookDaoImpl"></bean>
    <bean id="bookService" class="com.mark.service.impl.BookServiceImpl"></bean>
  • bean别名配置
  • name标签 可以配置多个别名 用空格、逗号、或者分号隔开

  • 在获取bean时可以使用别名获取,在注入依赖关系时也可以使用别名,但推荐使用id注入ref

BookService bookService = (BookService) ctx.getBean("service");
bookService.save();
  • 获取bean无论时通过id获取还是name获取,如果无法获取到,抛出异常: NoSuchBeanDefinitionException: No bean named 'service4' available 这时要检查配置bean的名字和获取bean时的名字是否对应
  • bean作用范围配置
public static void main(String[] args) {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");

    BookDao bookDao1 = (BookDao) ctx.getBean("dao");
    BookDao bookDao2 = (BookDao) ctx.getBean("dao");
    System.out.println(bookDao1);
    System.out.println(bookDao2);
}

//com.mark.dao.impl.BookDaoImpl@37918c79
//com.mark.dao.impl.BookDaoImpl@37918c79

可以发现Spring默认创建的bean是一个单例 当我们想要创建非单例时就需要在bean标签中添加一个属性: scope
scope即作用范围
+ singleton:不写scope时的默认值
+ prototype:此时Spring造出的对象并不是同一个对象,即非单例对象


public static void main(String[] args) {
    ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");

    BookDao bookDao1 = (BookDao) ctx.getBean("dao");
    BookDao bookDao2 = (BookDao) ctx.getBean("dao");
    System.out.println(bookDao1);
    System.out.println(bookDao2);
}
//com.mark.dao.impl.BookDaoImpl@37918c79
//com.mark.dao.impl.BookDaoImpl@78e94dcf
  • 为什么bean默认为单例?
    • 当默认不是单例,每次使用对象都会创建一个新的对象,这对内存会造成影响。而使用单例并不会对业务实现造成影响。
    • Spring就是管理那些可以复用的对象
  • 哪些bean适合造单例呢?
    • 表现层对象:如Servlet
    • 业务层对象:如Service
    • 数据层对象:如Dao
    • 工具对象
  • 不适合交给容器进行管理的bean:
    • 封装实体的域对象:如domain/pojo

2.7 bean的实例化

  • bean是如何创建的
  • bean本质上就是对象, 创建bean使用构造方法完成
private BookDaoImpl() {
    System.out.println("book dao constructor is running...");
}

在创建bookDao时会调用此构造方法,并且无论构造方法是私有还是公共的都可以调用成功 但是,当构造方法有参数时,就无法调用成功。 抛出异常从最后一个异常往上看NoSuchMethodException: com.mark.dao.impl.BookDaoImpl.<init>()</init>:没有无参构造方法 Failed to instantiate:错误的实例化 BookDaoImpl]: No default constructor found:该类没有发现默认的构造方法 BeanCreationException:创建Bean异常 Error creating bean with name 'bookDao' defined in class path resource [applicationContext.xml]: Instantiation of bean failed:错误地创建了一个在applicationContext.xml中定义的一个名叫bookDao的bean,实例化失败
* 实例化bean的三种方式
– ⭐使用 构造方法实例化bean(常用)
+ 提供可访问的构造方法(写不写都可以)

public class BookDaoImpl implements BookDao {

    /*
    private BookDaoImpl() {
        System.out.println("book dao constructor is running...");
    }
    */

    @Override
    public void save() {
        System.out.println("book dao save ...");
    }
}
+ 配置 <bean id="bookDao" class="com.mark.dao.impl.BookDaoImpl"></bean>
+ 无参构造方法如果不存在,将排除异常BeanCreationException
  • 静态工厂实例化bean(一般用于早期遗留的系统,了解即可)
    • 静态工厂
public static OrderDao getOrderDao(){
    System.out.println("factory setup...");
    return new OrderDaoImpl();
}
+ 配置

  • 实例工厂初始化bean(了解)
    • 实例工厂
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}
+ 配置

  • ⭐使用 FactoryBean实例化bean(第三种初始化的变种、 实用
    • 创建FactoryBean
public class UserDaoFactoryBean implements FactoryBean {
    /**
     * 代替原始实例工厂中创建对象的方法
     * @return new 对象
     * @throws Exception
     */
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    /**
     * 对象的类型
     * @return 对象.class
     */
    @Override
    public Class getObjectType() {
        return UserDao.class;
    }

    /**
     * 设置非单例
     * @return false
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}

+ 配置

2.8 bean的生命周期控制

  • 生命周期:从创建到消亡的完整过程
  • bean生命周期:bean从创建到销毁的整体过程
  • 初始化容器
    1. 创建对象(内存分配)
    2. 执行构造方法
    3. 执行属性注入(set操作)
    4. 执行bean初始化方法
  • 使用bean
    • 执行业务操作
  • 关系/销毁容器
    • 执行bean销毁方法
  • bean生命周期控制:在bean创建后到销毁前做一些事情
  • 如何 配置bean生命周期的方法?方式一: 配置方式
  • 在实现类创建两个方法
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }

    /**
     * 表示bean初始化对应的操作
     */
    public void init(){
        System.out.println("init...");
    }

    /**
     * 表示bean销毁前对应的操作
     */
    public void destory(){
        System.out.println("destory...");
    }
}
  • 配置两个方法

  • 在程序运行结束后就会关闭虚拟机,导致销毁的方法并没有机会执行
  • 在虚拟机退出之前,关闭IoC容器(暴力方式)
public class AppForLifeCycle {
    public static void main( String[] args ) {
//        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");

        bookDao.save();

        ctx.close();
    }
}
  • 设置关闭钩子
public class AppForLifeCycle {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //注册关闭钩子(任何时刻都可以)
        ctx.registerShutdownHook();

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");

        bookDao.save();

        //ctx.close();
    }
}
  • 使用 接口配置bean生命周期的方法:不需要配置初始化和销毁方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

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

    /**
     * bean销毁前执行
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("Service destory...");
    }

    /**
     * 在属性设置完之后初始化bean
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Service init...");
    }
}

2.9 依赖注入方式

  • 思考:
  • 向一个类中传递数据的方式有几种?
    • 普通方法(set方法)
    • 构造方法
  • 依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
    • 引用类型
    • 简单类型(基本数据类型与String)
  • 依赖注入方式
  • setter注入
    • 简单类型
    • 在bean中定义简单类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao {

    private int connectionNum;
    private String dataBaseName;

    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }

    public void setDataBaseName(String dataBaseName) {
        this.dataBaseName = dataBaseName;
    }

    @Override
    public void save() {
        System.out.println("book dao save ..."+connectionNum+","+dataBaseName);
    }
}
  * 配置中使用property标签value属性注入简单类型数据

+ **引用类型**
  * 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService {
    private BookDao bookDao;

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

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
  * 配置中使用property标签的ref属性注入引用类型对象

  • 构造器注入
    • 简单类型
    • 在bean中定义简单类型属性并提供可访问的构造方法
public class BookDaoImpl implements BookDao {
    private int connectionNum;
    private String dataBaseName;

    public BookDaoImpl(int connectionNum, String dataBaseName) {
        this.connectionNum = connectionNum;
        this.dataBaseName = dataBaseName;
    }

    @Override
    public void save() {
        System.out.println("book dao save ...");
    }
}
  * 配置中使用constructor-arg标签value属性注入简单类型数据

    - **解决形参会变名的问题:与形参名不耦合**

    - 根据参数位置匹配注入简单类型数据:解决参数的类型重复问题

+ **引用类型**
  * 在bean中定义引用类型属性并提供可访问的构造方法
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }

    @Override
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}
  * 配置中使用constructor-arg标签的ref属性注入引用类型对象

  • 两种依赖注入方式的选择
  • 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  • 可选依赖使用setter注入进行,灵活性强
  • Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  • 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  • 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  • *自己开发的模块推荐使用setter注入

2.10 依赖自动装配

  • IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
  • 自动装配方式
  • 按类型(常用)

前提是需要提供set方法
– 按名称


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

2.11 集合注入

  • 数组

        100
        120
        140

  • List

        王昭君
        嫦娥
        李元芳

  • Set

        王昭君
        王昭君
        嫦娥
        嫦娥

  • Map

  • Properties

        China
        Shaanxi
        Baoji

2.12 案例:数据源对象管理

  • 第三方资源配置管理
  • 导入druid坐标

    com.alibaba
    druid
    1.1.16

  • 配置数据源对象作为spring管理的bean

2.13 加载properties文件

  1. 开启context命名空间

  1. 使用context空间加载properties文件

  1. 使用属性占位符${}读取文件中的属性

  • 注意:
  • 当配置文件的属性名与系统的环境变量值相同时,获取到的是系统变量值

上述配置读出的username一定是电脑用户名 因此想要避免读取系统变量值,可以在加载文件时添加system-properties-mode属性


  • 当要加载多个配置文件时可以使用逗号隔开,也可以使用*.properties加载全部properties文件


标准格式


从类路径或jar包中搜索并加载properties文件:


2.14 容器

  • 创建容器
  • 加载类路径下的配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. 从文件系统下加载配置文件
ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\Code\\IJavaMySpring\\Spring_10_Container\\src\\main\\resources\\applicationContext.xml");
  • 获取bean
  • 方式一:使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
  1. 方式二:使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
  1. 方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);

对应的Bean只能有一个

MyBatis&Spring Framrwork
* BeanFactory
public class AppForBeanFactory {
    public static void main(String[] args) {
        Resource resources = new ClassPathResource("applicationContext.xml");
        BeanFactory bf = new XmlBeanFactory(resources);
        BookDao bookDao = bf.getBean(BookDao.class);
        bookDao.save();
    }
}

BeanFactory完毕后,所有的bean均为延迟加载,而ApplicationContext是全部立即加载

2.15 注解开发

  • 注解开发定义bean
  • 使用 @Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }
}

Component是组件的意思,该注解可以代替配置 <bean id="bookDao" class="com.mark.dao.impl.BookDaoImpl"></bean>
– 核心配置文件中通过组件扫描加载bean


  • Spring提供了 @Component注解的三个衍生注解
  • @Controller:用于表现层bean定义
  • @Service:用于业务层bean定义
@Service
public class BookServiceImpl implements BookService {

}
  • @Repository:用于数据层bean定义
@Repository("bookDao")
//Repository是仓库的意思,这里译为数据仓库
public class BookDaoImpl implements BookDao {

}
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
}
//com.mark.dao.impl.BookDaoImpl@459e9125
//com.mark.service.impl.BookServiceImpl@692f203f
  • 纯注解开发 Spring3.0升级了纯注解开发模式,使用Java类代替了配置文件,开启了Spring快速开发通道
  • 在config包下创建类 SpringConfig添加注解 @Configuration,用于设定当前类为配置类,这里的@Configuration就代替了配置文件的

  • 添加注解 @ComponentScan("com.mark"),用于设定扫描路径,此注解只能添加一次, 多个数据需要用数组格式,这个注解就代替了 <context:component-scan base-package="com.mark"></context:component-scan>
@Configuration
//@ComponentScan("com.mark")
@ComponentScan({"com.mark.dao","com.mark.service"})
public class SpringConfig {

}
  • 创建容器时使用注解配置ctx:AnnotationConfigApplicationContext
public class AppForAnnotation {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
}

2.16 bean管理

  • bean作用范围
  • 注解@Scope
    • singleton:单例模式
    • prototype:非单例模式
  • bean生命周期
  • @PostConstruct注解:初始化方法
  • @PreDestroy注解:摧毁方法 两个注解添加前要增加jsr-api坐标
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }

    @PostConstruct
    public void init() {
        System.out.println("book dao init ...");
    }

    @PreDestroy
    public void destory() {
        System.out.println("book dao destory ...");
    }
}

2.17 依赖注入

  • 自动装配
  • 在bean中添加 @Autowired注解
@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    /*
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    */

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

@Autowired注解根据类型装配,使用暴力反射注入,即使没有set方法依旧可以成功
– 当有两个相同类型的类时,就不能用类型装配,需要使用名字注入,使用注解 @Qualifier,而且@Autowired不可以去掉,@Qualifier无法单独使用

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

public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
}

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

  • 使用@Value实现简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("Mark")
    private String name;

    @Override
    public void save() {
        System.out.println("book dao save ..."+name);
    }
}
  • 将配置文件的数据注入简单类型
    • 在配置类添加注解 @PropertySource
@Configuration
@ComponentScan("com.mark")
@PropertySource("jdbc.properties")
public class SpringConfig {

}
+ 获取值直接使用占位符${},里面写属性名即可
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;

    @Override
    public void save() {
        System.out.println("book dao save ..."+name);
    }
}
 如果配置文件有多个,可以使用数组形式: @PropertySource("{jdbc.properties,jdbc.propertiesjdbc.properties}") 这里不支持使用通配符: *.properties

## 2.18 第三方bean管理

* 使用 @Bean配置第三方bean


@Configuration
public class SpringConfig {
    //1.定义一个方法,获得要管理的对象
    //2.添加@Bean,表示当前方法的返回值是一个bean

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds =new DruidDataSource();

        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql:///spring_db");
        ds.setUsername("root");
        ds.setPassword("123");

        return ds;
    }
}
  • 不建议将该@Bean写到SpringConfig中,应当使用第三方的配置类管理第三方bean
  • 方式一: 导入式
public class JDBCConfig {
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds =new DruidDataSource();

        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql:///spring_db");
        ds.setUsername("root");
        ds.setPassword("123");

        return ds;
    }
}
+ **使用 @Import 注解手动加入配置到核心配置**,此注解只添加一次,多个配置类要用数组格式
@Configuration
@Import(JDBCConfig.class)
public class SpringConfig {

}
  • 方式二:扫描式(不推荐)
    +
@Configuration
public class JDBCConfig {
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds =new DruidDataSource();

        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql:///spring_db");
        ds.setUsername("root");
        ds.setPassword("123");

        return ds;
    }
}
+ 使用 @ComponentScan注解扫描配置类所在的包,加载对应的配置类信息
@Configuration
@ComponentScan("com.mark.config")
public class SpringConfig {

}
  • 为第三方bean注入资源
  • 简单类型依赖注入
public class JDBCConfig {
    @Value("com.mysql.cj.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql:///spring_db")
    private String url;
    @Value("root")
    private String username;
    @Value("123")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds =new DruidDataSource();

        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);

        return ds;
    }
}
  • 引用类型注入
public class JDBCConfig {
    @Bean
    public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
        DruidDataSource ds =new DruidDataSource();

        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);

        return ds;
    }
}
+ 引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象

2.19 XML配置和注解配置对比

MyBatis&Spring Framrwork

2.20 Spring整合MyBatis

MyBatis&Spring Framrwork
* 步骤:
导入所需坐标

        org.springframework
        spring-context
        5.2.10.RELEASE

        com.alibaba
        druid
        1.1.16

        org.mybatis
        mybatis
        3.5.6

        mysql
        mysql-connector-java
        8.0.29

        org.springframework
        spring-jdbc
        5.2.10.RELEASE

        org.mybatis
        mybatis-spring
        1.3.0

  • 添加MyBatisConfig配置类
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb =new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.mark.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc =new MapperScannerConfigurer();
        msc.setBasePackage("com.mark.dao");
        return msc;
    }
}

第一个bean代替了


第二个bean代替了


2.21 Spring整合Junit

  • 添加依赖

    junit
    junit
    4.11
    test

    org.springframework
    spring-test
    5.2.10.RELEASE

  • 给测试类添加注解
//使用Spring整合Junit专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//指定Spring的配置
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        System.out.println(accountService.findById(2));
    }
}

2.22 AOP简介

  • AOP核心概念 AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构 ​ OOP(Object Oriented Programming)面向对象编程
  • AOP作用 在不惊动原始设计的基础上为其进行功能增强
    MyBatis&Spring Framrwork
  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点(Pointcut):匹配连接点的式子
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
    • 一个具体方法:com.mark.dao包下的BookDao:接口中的无形参无返回值的save方法
    • 匹配多个方法:所有的save方法、所有的get开头的方法、所有以Dao结尾的接口中的任意方法、所有带有一个参数的方法
  • 通知(Advice)在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面(Aspect):描述通知与切入点的对应关系: *执行位置与共性功能的关系

2.23 AOP入门案例

要求:在接口执行前输出当前系统时间

开发模式:XML or 注解

步骤:

  • 导入坐标

        org.springframework
        spring-context
        5.2.10.RELEASE

        org.aspectj
        aspectjweaver
        1.9.4

  • 制作连接点方法(原始操作,Dao接口与实现类)
@Repository
public class BookDaoImpl implements BookDao {

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

    @Override
    public void update(){
        System.out.println("book dao update ...");
    }
}
  • 制作共性功能(通知类与通知)
  • 定义切入点
  • 切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑
  • 绑定切入点与通知关系(切面)
//通知类必须配置成Spring管理的bean
@Component
//1.告诉Spring,MyAdvice是用来做AOP的
@Aspect
public class MyAdvice {
    //3.定义切入点
    @Pointcut("execution(void com.mark.dao.BookDao.update())")
    private void pt(){}

    //4.绑定切入点和通知
    @Before("pt()")
    //2.定义通知
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

  • Spring核心配置中标明使用注解开发AOP
@Configuration
@ComponentScan("com.mark")
@EnableAspectJAutoProxy
public class SpringConfig {
}

2.24 AOP工作流程

  • Spring 容器启动
  • 读取 所有切面配置中的切入点,即已经在@Before中表明的切入点
  • 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
  • 匹配失败,创建对象
  • 匹配成功, 创建原始对象(目标对象)的代理对象
  • 获取bean执行方法
  • 获取bean,调用方法并执行,完成操作
  • 获取的bean是代理对象时, 根据代理对象的运行模式运行原始方法与增强的内容,完成操作
  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

2.25 AOP切入点表达式

切入点:要进行增强的方法

切入点表达式:要进行增强的方法的描述方式

  • 切入点表达式描述方式:
  • 描述方式一:执行com.mark.dao包下的BookDao接口中的无参数update方法 execution(void com.mark.dao.BookDao.update())
  • 描述方式二:执行com.mark.dao.impl包下的BookDaoImpl类中的无参数update方法 execution(void com.mark.dao.BookDaoImpl.update())
  • 切入点表达式标准格式&#x52A8;&#x4F5C;&#x5173;&#x952E;&#x5B57;(&#x8BBF;&#x95EE;&#x4FEE;&#x9970;&#x7B26; &#x8FD4;&#x56DE;&#x503C; &#x5305;&#x540D;.&#x7C7B;/&#x63A5;&#x53E3;&#x540D;.&#x65B9;&#x6CD5;&#x540D;(&#x53C2;&#x6570;) &#x5F02;&#x5E38;&#x540D;)
  • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定的切入点
  • 访问修饰符:public,private等,可省略
  • 返回值
  • 包名
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略
  • 通配符 可以使用通配符描述切入点,快速描述
  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配夫出现 @Pointcut("execution( * com.mark.*.BookDao.find*(*))") 表示匹配com.mark包下任意包中的BookDao类或接口中所有find开头的带有一个参数的方法
  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写 @Pointcut("execution(public User com..BookDao.findById(..))") 表示匹配com包下的任意包中的BookDao类或接口中所有名称为findById的方法
  • +:装用于匹配子类类型 @Pointcut("execution(* *..*Service+.*(..))") 表示匹配任意返回值任意包下的以Service结尾的类或者接口的子类的任意参数的任意方法
  • 书写技巧
  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述( 可省略访问控制修饰符描述
  • 返回值类型对于 增删改类使用精准类型加速匹配,对于 查询类使用*通配快速描述
  • 包名书写尽量不使用..匹配,效率过低, 常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的 采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写 以动词进行精准匹配名词采用*匹配,例如getByld书写成getBy*,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常 不使用异常作为 匹配规则 @Pointcut("execution(* com.mark.*.*Service.find*(..))")

2.26 AOP通知类型

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置

AOP通知共分为5种类型

  • 前置通知
@Before("pt()")
public void before() {
    System.out.println("before advice ...");
}
  • 后置通知
@After("pt()")
public void before() {
    System.out.println("before advice ...");
}
  • 环绕通知(重点、常用)
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    //表示对原始操作的调用
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

@Around注意事项
环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
– 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果 接收返回值,必须设定为Object类型
– 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
– 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
* 返回后通知(了解)

@AfterReturning("pt2()")
public void afterReturning() {
    System.out.println("afterReturning advice ...");
}

只有方法没有抛出异常正常结束时才会运行
* 抛出异常后通知(了解)

@AfterThrowing
public void afterThrowing() {
    System.out.println("afterThrowing advice ...");
}

只有方法抛出异常后才会运行

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

  • 需求:任意业务层接口执行均可显示其执行效率(执行时长)
  • 分析:
  • 业务功能:业务层接口执行前后分别记录时间
  • 通知类型选择前后均可增强的类型:环绕通知
  • 实现:
@Component
@Aspect
public class AppAdvice {
//匹配业务层所有方法
@Pointcut("execution(* com.mark.service.*Service.*(..))")
private void servicePt() {
}

@Around("AppAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//代表一次执行的签名信息
Signature signature = pjp.getSignature();
//类型
//System.out.println(signature.getDeclaringType());
//类型名
//System.out.println(signature.getDeclaringTypeName());
//方法名
//System.out.println(signature.getName());
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}

long end = System.currentTimeMillis();

System.out.println("万次执行" + className + "." + methodName + "所用时间:" + (end - start) + "ms");
}
}
</code></pre> <h2>2.28 AOP通知获取数据</h2> <ul> <li><strong>获取切入点方法的参数</strong></li> <li><code>JoinPoint</code>:适用于前置、后置、返回后、抛出异常后通知</li> </ul> <pre><code class="language-java">@Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("before advice ..." ); } @After("pt()") public void after(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("after advice ..."); } </code></pre> <ul> <li><strong> <code>ProceedJointPoint</code> :适用于环绕通知</strong></li> </ul> <pre><code class="language-java">@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); args[0]=666; //System.out.println(Arrays.toString(args)); Object ret = pjp.proceed(args); return ret; } </code></pre> <ul> <li>获取切入点方法的返回值</li> <li>返回后通知</li> </ul> <pre><code class="language-java">//ret作为返回值 returning要和形参对应 @AfterReturning(value = "pt()",returning = "ret") public void afterReturning(Object ret) { System.out.println("afterReturning advice ..."+ret); } </code></pre> <p>如果要获取参数添加JoinPoint, <code>JoinPoint jp</code>必须在前</p> <pre><code class="language-java">@AfterReturning(value = "pt()",returning = "ret") public void afterReturning(JoinPoint jp, Object ret) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("afterReturning advice ..."+ret); } </code></pre> <ul> <li>环绕通知</li> </ul> <pre><code class="language-java">@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return ret; } </code></pre> <ul> <li>获取切入点方法的异常</li> <li>抛出异常后通知</li> </ul> <pre><code class="language-java">@AfterThrowing(value = "pt()",throwing = "t") public void afterThrowing(Throwable t) { System.out.println("afterThrowing advice ..."+t); } </code></pre> <ul> <li>环绕通知</li> </ul> <pre><code class="language-java">@Around("pt()") public Object around(ProceedingJoinPoint pjp){ Object ret = null; try { ret = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } return ret; } </code></pre> <h2>2.28 百度网盘密码数据兼容处理</h2> <ul> <li>需求:对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理</li> <li>分析:</li> <li>在业务方法执行之前对所有的输入参数进行格式处理:trim()</li> <li>实现:</li> </ul> <pre><code class="language-java">@Component @Aspect public class DataAdvice { @Pointcut("execution(boolean com.mark.service.ResourcesService.openURL(*,*))") private void servicePt() { } @Around("DataAdvice.servicePt()") public Object trimStr(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); for (int i = 0; i < args.length; i++) { //判断参数是不是字符串 if (args[i].getClass().equals(String.class)) { args[i] = args[i].toString().trim(); } } Object ret = pjp.proceed(args); return ret; } } </code></pre> <h2>2.29 Spring事务简介</h2> <p>事务作用:在数据层保障一系列的数据库操作同成功同失败</p> <p>Spring事务作用:在 <strong>数据层或业务层</strong>保障一系列的数据库操作同成功同失败</p> <p><img alt="MyBatis&Spring Framrwork" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/2326431-20220903124117483-2138993351.png" /></p> <ul> <li>案例:模拟银行账户间转账业务</li> <li>需求:实现任意两个账户间转账操作</li> <li>需求微缩:A账户减钱、B账户加钱</li> <li>分析:<ul> <li>数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)</li> <li>业务层提供转账操作(transfer),调用减钱与加钱的操作</li> <li>提供两个账号和操作金额执行转账操作</li> <li>基于Spring整合MyBatis环境搭建上述操作</li> </ul> </li> <li>结果分析:<ul> <li>程序正常执行时,账户金额A减B加</li> <li>程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败</li> </ul> </li> <li>实现:<ul> <li><strong>给要添加事务的实现类的接口添加注解 <code>@Transactional</code></strong></li> </ul> </li> </ul> <pre><code class="language-java">@Transactional public void transfer(String out,String in ,Double money) ; </code></pre> <p>Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合 <strong>注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务</strong> + <strong>在JDBC配置类中添加事务管理器配置 <code>PlatformTransactionManager</code></strong></p> <pre><code class="language-java">@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager ptm = new DataSourceTransactionManager(); ptm.setDataSource(dataSource); return ptm; } </code></pre> <pre><code>+ **在Spring配置类中打开使用注解式事务驱动 @EnableTransactionManagement** </code></pre> <pre><code class="language-java">@Configuration @ComponentScan("com.mark") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig { } </code></pre> <h2>2.30 Spring事务角色</h2> <p><img alt="MyBatis&Spring Framrwork" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/2326431-20220903124145936-1884787199.jpg" /></p> <ul> <li>事务角色</li> <li><strong>事务管理员</strong>: <strong>发起事务方</strong>,在Spring中通常指代业务层 <strong>开启事务的方法</strong></li> <li><strong>事务协调员</strong>: <strong>加入事务方</strong>,在Spring中通常指代 <strong>数据层方法</strong>, *<em>也可以是业务层方法</em></li> </ul> <h2>2.31 事务相关配置</h2> <p><img alt="MyBatis&Spring Framrwork" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/2326431-20220903124156659-790268283.png" /></p> <pre><code class="language-java">//开启只读、永不超时 @Transactional(readOnly = true,timeout = -1) public void transfer(String out,String in ,Double money) ; </code></pre> <p><strong>除了运行时异常和Error系异常会回滚,除此之外都不回滚</strong>,即便出现异常也不回滚,如 <code>IOException()</code> 为了保证正确回滚, <strong>需要添加属性 <code>rollbackFor</code></strong></p> <pre><code class="language-java">@Transactional(rollbackFor = {IOException.class}) public void transfer(String out,String in ,Double money) throws IOException; </code></pre> <ul> <li>案例:转账业务追加日志</li> <li>需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕</li> <li>需求微缩:A账户减钱、B账户加钱,数据库记录日志</li> <li>分析:<ul> <li>基于转账操作案例添加日志模块,实现数据库中记录日志</li> <li>业务层转账操作(transfer),调用减钱、加钱与记录日志功能</li> </ul> </li> </ul> <pre><code class="language-java">public interface LogDao { @Insert("insert into tnl_log (info,createDate) values(#{info},now())") void log(String info); } </code></pre> <pre><code class="language-java">public interface LogService { @Transactional void log(String out, String in, Double money); } </code></pre> <pre><code class="language-java">@Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Override public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } } </code></pre> <ul> <li>实现效果预期:<ul> <li>无论转账操作是否成功,均进行转账操作的日志留痕</li> </ul> </li> </ul> <pre><code class="language-java">@Override public void transfer(String out,String in ,Double money) throws IOException{ try { accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } } </code></pre> <ul> <li>存在的问题<ul> <li>日志的记录与转账操作隶属于同一个事务,同成功同失败</li> </ul> </li> <li>实现效果预期改进:<ul> <li>无论转账操作是否成功,日志必须保留 <img alt="MyBatis&Spring Framrwork" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/2326431-20220903124215336-1095730029.png" /></li> </ul> </li> <li>在业务层接口上添加Spring事务,设置事务传播行为 <strong>REQUIRES_NEW</strong>(需要新事务)</li> </ul> <p>
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}

MyBatis&Spring Framrwork

Original: https://www.cnblogs.com/hackertyper/p/16652390.html
Author: 风吹头蛋凉OvO
Title: MyBatis&Spring Framrwork

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575106/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球