Spring5全面详解

15 篇文章 0 订阅
7 篇文章 2 订阅

Spring 5 完整且快速上手

此文章并已完结,如有错误请评论区指正。还望海涵。
目前更新到:
Spring5事务操作
所有的Jar包和相关依赖,会在文章跟新结束后,统一上传至,百度云盘。
目前已经完全书写完毕了,所对应的Jar包链接

链接:https://pan.baidu.com/s/1X85ob4gcxrelhPWRnRjb5w
提取码:0qle


1. 框架概述

1. Spring是轻量级,开源的JavaEE框架。

2. Spring可以解决企业应用开发的复杂性。

3. Spring中有两大核心部分:IOC, AOP
	(1) IOC: 控制反转,把创建对象的过程交给Spring进行管理,也就是Spring帮我们创建对象。进行对象的实例化
	(2) AOP: 面向切面,在不修改源代码的情况下,去进行功能的添加或者增强。

4. 关于Spring框架相关的特点:
	(1) 方便解耦,简化开发
	(2) AOP编程的支持
	(3) 方便程序的测试
	(4) 方便继承各种优秀的框架
	(5) 降低JavaEE API 的使用难度
	(6) 声明事务的管理

2. 入门案例

下载spring jar包依赖

1. 要先进入Spring的官方网站, https://spring.io/

2. 找到Projects 下的 SpringFromwork

3. 然后点击LEARN 查看所有的Spring版本, 目前最新的为: 5.3.8 CURRENT GA    GA表示稳定版

4. 如何下载
	https://repo.spring.io/release/org/springframework/spring/ 下载对应的dist压缩包解压。

5. libs 文件夹下 有对应的jar包

打开idea 创建一个普通的java工程

导入Spring5 的相关jar包

Beans, Core, Context, Expression

type: spring-beans-5.3.8.jar

在根目录下新建一个 libs文件夹,导入对应的jar包


开始工作

首先创建一个类

// User类
public class User {
    
    public void add() {
        sout("add........");
    }
    
}

在spring里,注册对象有两种方式,一种 是配置文件 ,一种是注解方式,现在暂时先做入门 ,使用配置文件来书写创建对象的实例。

创建spring5的配置文件

(1) 在spirng5里面 是使用xml文件配置的。
(2) 创建 xml文件, 为了方便,在src目录下创建,起名为 beans.xml 名字可以随便起,我习惯起名为这个

固定写法

<?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">

</beans>

把User类注册到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">

    <!-- 配置User类的创建-->
    <bean id="user" class="com.wang.spring5.User" />

</beans>

在测试类中测试是否可以获取到User类

public class MyTest {
    
    @Test
    public void MyUserTest() {
        /* 个人习惯把测试类的方法名称写为测试的对象+test */
        
        // 加载Spring 配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        
        // 取出容器中托管的对象
        /* 方法getBean所传入的参数就是配置user的时候给的id参数 */
        User user = context.getBean("user", User.class);
        
        // 调用方法
        user.add();	// 输出  add........
    }
    
}

3. IOC容器

(1) IOC底层原理 (基本的概念和原理)

1. 什么是IOC
   (1) 控制反转,把对象的创建,和对象之间的调用的过程,都交给Spring管理。
   (2) 使用IOC的目的,是为了让代码和代码之间的耦合度降低。

2. IOC的底层原理
	(1) XML解析技术
	(2) 工厂设计模式
	(3) 反射技术
使用最原始的方法创建对象
public class User {
    
    public void add() {
        System.out.println("add....User");
    };
    
}

class UserTest() {
    
    public static void main(String[] args) {
        
        User user = new User();
        
        user.add();	// add....User
        
    }
    
}
工厂模式创建对象
/* 在Service调用dao */
public class UserDao {

    public void add() {
        System.out.println("add...");
    }

}


public class UserFactory {

    public static UserDao getUserDao() {
        return new UserDao();
    }

}


public class UserServer {

    public static void execute() {

        UserDao userDao = UserFactory.getUserDao();

        userDao.add();

    }

    public static void main(String[] args) {
        execute();  // add...
    }

}

最终的目的无非是最低的降低耦合度。

解耦的过程

第一步肯定有一个创建xml文件的过程,使用Bean 配置要创建的对象。

<bean id="getBean时使用的名字" class="全类名" ></bean>

<bean id="userDao" class="com.wang.UserDao"></bean>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i19TzeDx-1624245900942)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1623553638277.png)]

(2) IOC接口(2个)

1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
2. Spring提供了IOC容器实现的两种方式(两个接口):
	(1) BeanFactory
	(2) ApplicationContext
BeanFactory
BeanFactory是IOC容器中最基本的一种实现,是Spring内置的一种方式。一般开发中不会去使用。

<!--
    加载配置文件的时候,他不会去创建里面的对象。
	在获取或者使用的时候,才会去创建这个对象。
	如 getBean.

	实测好像并没有这个区别,都会在获取xml 的时候进行对象的创建。
-->
ApplicationContext
ApplicationContext 是 BeanFactory接口的 一个 子接口。 这个接口中比	BeanFactory中提供了更多更强大的功能
这个接口一般是面向开发人员使用的。

ApplicationContext 下的子接口和实现类

(3) IOC操作Bean管理(基于XML)

什么是Bean管理
其实指的是两个操作
	(1) Spring给我们创建对象、
	(2) Spring对属性的注入

Bean管理有两种实现方式:
	(1) 基于XML文件方式配置
	(2) 基于注解方式配置
Spring 基于XML方式管理Bean
<!-- 1. 基于XML的方式创建对象 -->
上面已经做过无数次,不在示范。

<!-- 2. 基于XML的方式注入属性 -->
Spring在创建对象的时候,默认会去走无参构造器。 如果没有无参构造,则会报错
(1) DI ---> 依赖注入, 就是注入属性。  
DI 是 IOC 中的一种具体实现, 表示依赖注入,也就是注入属性,需要在创建对象的基础上完成。 

Bean常用属性
1. id 属性
	唯一标识
2. class 属性
	类全路径(包类路径)
3. name 属性
	已弃用
原始方法注入属性
package com.wang.spring5.text;

public class Book {

    private String name;

    /*
    有多种注入属性的方式(原始方法), 通常为 set设置属性和 有参构造。
    */

    public Book() { }

    public Book(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public static void main(String[] args) {
        // set方式
        Book book = new Book();
        book.setName("《算法导论》");

        System.out.println(book.getName());  // 《算法导论》

        // 有参构造方式
        Book book1 = new Book("《Java核心技术卷 I》");

        System.out.println(book1.getName()); // 《Java核心技术卷 I》


    }

}

Spring的方式注入属性(Set方式)

Java代码

public class Book {

    private String name;

    private int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

XML

<!-- 在Spring里先配置对象的创建,然后在管理属性的注入 -->
<bean id="book" class="com.wang.spring5.text.Book">
    <!--
        property 完成属性的注入
        name: 类里面属性的名称
        value: 值
        -->
    <property name="name" value="《算法导论》" />
    <property name="price" value="119" />
</bean>

测试代码类

import com.wang.spring5.text.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyText {
    
    @Test
    public void MyBook() {
        
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        
        Book book = context.getBean("book", Book.class);
        
        System.out.println(book.getName());	// 《算法导论》

        System.out.println(book.getPrice()); // 119
        
    }
    
}

所有的属性注入都需要在创建对象的基础上去进行

Spring的方式注入属性(有参构造)

Java类

public class Orders {

    // 属性
    private String ordersName;

    private String  address;

    public Orders() {
    }

    // 有参数的构造
    public Orders(String ordersName, String address) {
        this.ordersName = ordersName;
        this.address = address;
    }
   
    @Override
    public String toString() {
        return "Orders{" +
                "ordersName='" + ordersName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

}

XML

<!-- Orders 有参构造注入属性-->
<bean id="orders" class="com.wang.spring5.text.Orders">
    <!-- 
		constructor-arg 还有一个属性叫index
		表示了对应的构造方法里,第一个参数
		<constructor-arg index="0" value="" />
	-->
    <constructor-arg name="ordersName" value="小熊饼干" />
    <constructor-arg name="address" value="河南郑州" />
</bean>

测试代码

/* 前缀太简单了。直接省略掉 自己补 */
@Test
public void MyOrders() {

    ApplicationContext context =
        new ClassPathXmlApplicationContext("beans.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println(orders.toString());  // Orders{ordersName='小熊饼干', address='河南郑州'}

}
P命名空间注入(了解)

p命名空间注入,可以简化我们基于 xml 配置方式, 但是在实际中用的并不多

xmlns:p="http://www.springframework.org/schema/p"

添加p命名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kvnavzzv-1624245900944)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1623809420411.png)]

案例

public class Book {

    private String name;

    private int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}
 <!-- p注入Book类-->
<bean id="book2" class="com.wang.spring5.text.Book" p:name="《Spring实战》" p:price="99" />
@Test
public void MyBook2() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    Book book2 = context.getBean("book2", Book.class);

    System.out.println(book2.getName());  // 《Spring实战》

    System.out.println(book2.getPrice());  // 99

}

XML注入其他类型的属性
XML注入Null值

Java Book类

package com.wang.spring5.text;

public class Book {

    private String name;

    private String nullValue;

    private int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNullValue(String nullValue) {
        this.nullValue = nullValue;
    }

    @Override
    public String toString() {
        return "Book{" +
            "name='" + name + '\'' +
            ", nullValue='" + nullValue + '\'' +
            ", price=" + price +
            '}';
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

XML文件

<!-- 注入空值-->
<bean id="book3" class="com.wang.spring5.text.Book">
    <property name="name" value="《Java核心技术卷2》" />
    <property name="price" value="32" />
    <!-- 设置null值-->
    <property name="nullValue">
        <!-- 表示设置空值-->
        <null />
    </property>
</bean>

Java测试类

@Test
public void MyBook3() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    Book book3 = context.getBean("book3", Book.class);

    System.out.println(book3.toString());  
    // Book{name='《Java核心技术卷2》', nullValue='null', price=32}

}
XML注入特殊符号

使用场景

<bean id="xx" class="xx" name="xx" value="<<我我我>>"></bean>
这个时候,使用了 <<>> 会报错,因为他把小尖括号当成了你的标签括号

使用C数据  
注入属性-外部Bean
<!-- 场景-->
(1) 创建两个类 Service 和 Dao两个类
(2) Service 调用 DaO 类里面的方法。

原始的实现方式

首先创建两个包 Service 和 Dao

在Service下创建 UserService类, 在Dao下创建 UserDao接口,和对应的实现类

Java代码

/* UserDaoInterface */
package com.wang.spring5.Dao;

public interface UserDao {

    void update();

}

/* UserDaoImpl */
package com.wang.spring5.Dao;

public class UserDaoImpl implements UserDao{

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

/* UserService */
package com.wang.spring5.Service;

import com.wang.spring5.Dao.UserDao;
import com.wang.spring5.Dao.UserDaoImpl;

public class UserService {

    public void add() {

        System.out.println("service  add............");

        // 创建UserDao的对象
        UserDao dao = new UserDaoImpl();

        dao.update();

    }

}
基于XML的方式实现

基础操作同上,包名类名 。

<!-- service 和 dao 对象的创建-->
<bean id="service" class="com.wang.spring5.Service.UserService">
    <!--
        ref: 引用一个地址。 userDao要完全对应上 对应对象的 id名称。
     -->
    <property name="userDao" ref="userDao" />
</bean>
<!-- interface 是个接口 他不能new对象。所以我们找他的实现类。-->
<bean id="userDao" class="com.wang.spring5.Dao.UserDaoImpl">

</bean>

Java测试类

@Test
public void serviceAndDaoSetPro() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");

    UserService service = context.getBean("service", UserService.class);

    service.add();

}
注入属性-内部Bean
使用场景
<!-- 
	(1) 一对多关系: 部门和员工
 		一个部门可以有多个员工,一个员工 属于我们的某一个部门
		部门是一, 员工是多

	(2) 在实体类之间表示一对多的关系

-->

具体的一个实现内部Bean

先创建一个bean包,在包里创建一个Dept部门类。 里面有一个属性,部门名称, 书写对应的set方法和toString方法。

package com.wang.spring5.bean;
// 部门类
public class Dept {

    private String DeptName;

    public void setDeptName(String deptName) {
        DeptName = deptName;
    }

    @Override
    public String toString() {
        return "Dept{" +
            "DeptName='" + DeptName + '\'' +
            '}';
    }
}

创建员工类,Emp 有两个属性, 员工姓名和员工性别,书写对应的set方法,和toString方法;

员工属于某一个部门,所以有一个属性是 Dept dept;

package com.wang.spring5.bean;

public class Emp {

    private String EmpName;

    private String gender;

    // 员工属于某一个部门  使用对象表示
    private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    public void setEmpName(String empName) {
        EmpName = empName;
    }

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

    @Override
    public String toString() {
        return "Emp{" +
            "EmpName='" + EmpName + '\'' +
            ", gender='" + gender + '\'' +
            ", dept=" + dept +
            '}';
    }
}

xml的书写方式

<!-- 内部bean-->
<bean id="emp" class="com.wang.spring5.bean.Emp">
    <!-- 类里有三个属性, 前两个是普通属性,第三个属于对象属性-->
    <property name="empName" value="张三" />
    <property name="gender" value="" />
    <!-- 对象属性  这也是内部bean的方式-->
    <property name="dept">
        <bean class="com.wang.spring5.bean.Dept">
            <property name="deptName" value="安保部" />
        </bean>
    </property>
</bean>

Java测试类

public class MyTest3 {

    @Test
    public void Test1() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");

        Emp emp = context.getBean("emp", Emp.class);

        System.out.println(emp.toString());  
        // Emp{EmpName='张三', gender='男', dept=Dept{DeptName='安保部'}}

    }

}
注入属性-级联赋值

第一种写法

类依然是上面呢两个类, Emp和Dept 类,改变的只有Xml和 Test类

<!-- 级联赋值-->
<bean id="emp2" class="com.wang.spring5.bean.Emp">
    <!-- 类里有三个属性, 前两个是普通属性,第三个属于对象属性-->
    <property name="empName" value="张三" />
    <property name="gender" value="" />
    <!-- 级联赋值-->
    
    <property name="dept" ref="dept">

    </property>
</bean>
<bean  id="dept" class="com.wang.spring5.bean.Dept">
    <property name="deptName" value="财务部" />
</bean>

实则不然,其实test类改变的也只有 获取配置文件的文件名称和getBean方法的参数改变。

@Test
public void Test1() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    Emp emp = context.getBean("emp2", Emp.class);

    System.out.println(emp.toString()); 
    // Emp{EmpName='张三', gender='男', dept=Dept{DeptName='安保部'}}

}

注入数组类型属性

一个通用的Java类

package com.w.spring5.collectiontype;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Student {

    // 1. 数组类型的属性
    private String[] courses;

    // 2.list集合属性
    private List<String > list;

    // 3. Map集合类型
    private Map<String, String > maps;

    // 4. Set集合类型
    private Set<String > strings;

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setStrings(Set<String> strings) {
        this.strings = strings;
    }

    @Override
    public String toString() {
        return "Student{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", maps=" + maps +
                ", strings=" + strings +
                '}';
    }
}

注入数组

<!-- 数组属性注入-->
<property name="courses">
    <array>
        <value>语文</value>
        <value>数学</value>
        <value>英语</value>
        <value>地理</value>
    </array>
</property>
注入List集合类型属性
<!-- List注入-->
<property name="list">
    <list>
        <value>110</value>
        <value>99</value>
        <value>89</value>
        <value>101</value>
    </list>
</property>
注入Map集合类型属性
<!-- Map注入 -->
<property name="maps">
    <map>
        <entry key="" value="" />
        <entry key="" value="" />
        <entry key="" value="" />
        <entry key="" value="" />
    </map>
</property>
注入Set集合类型属性
<!-- set -->
<property name="strings">
    <set>
        <value>set1</value>
        <value>set2</value>
        <value>set3</value>
        <value>set4</value>
    </set>
</property>
完整的XML数组及集合注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 创建Student对象-->
    <bean id="student" class="com.w.spring5.collectiontype.Student">
        <!-- 数组属性注入-->
        <property name="courses">
            <array>
                <value>语文</value>
                <value>数学</value>
                <value>英语</value>
                <value>地理</value>
            </array>
        </property>
        <!-- List注入-->
        <property name="list">
            <list>
                <value>110</value>
                <value>99</value>
                <value>89</value>
                <value>101</value>
            </list>
        </property>
        <!-- Map注入 -->
        <property name="maps">
            <map>
                <entry key="" value="" />
                <entry key="" value="" />
                <entry key="" value="" />
                <entry key="" value="" />
            </map>
        </property>
        <!-- set -->
        <property name="strings">
            <set>
                <value>set1</value>
                <value>set2</value>
                <value>set3</value>
                <value>set4</value>
            </set>
        </property>
    </bean>

</beans>

在集合里注入对象类型的值
<!-- 5. 学生课程List <Course  -->
<property name="coursesList">
    <list>
        <!-- Bean对应的是 Bean容器对象的id-->
        <ref bean="course1" />
        <ref bean="course2" />
    </list>
</property>
</bean>

<!-- 创建多个Course对象 -->
<bean id="course1" class="com.w.spring5.collectiontype.Course">
    <property name="courseName" value="Spring" />
</bean>

<bean id="course2" class="com.w.spring5.collectiontype.Course">
    <property name="courseName" value="MyBatis" />
</bean>
把集合注入部分提取出来
在Spring配置文件中引入命名空间 util
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation=
       "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 集合类型属性注入-->
    <util:list id="bookList">
        <value>《Java核心技术卷 I》</value>
        <value>《Java核心技术卷 II》</value>
        <value>《Java编程思想》</value>
    </util:list>

    <!--  提取list集合类型属性注入使用-->
    <bean id="book" class="com.w.spring5.collectiontype.Book">
        <property name="list" ref="bookList" />
    </bean>

</beans>

/* Java测试代码 */

package com.w.spring5.test;

import com.w.spring5.collectiontype.Book;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void main() {

        ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("beans2.xml");

        Book book = applicationContext.getBean("book", Book.class);

        System.out.println(book.toString());
        // Book{list=[《Java核心技术卷 I》, 《Java核心技术卷 II》, 《Java编程思想》]}


    }

}

工厂Bean (FactoryBean)
1. Spring有两种类型的Bean,一种普通的Bean,另一种工厂Bean(FactoryBean)

他们之间有不同的区别,这是一个重要的概念。

普通Bean
	在Spring配置文件,你定义的类型,就是返回的类型。

工厂Bean
	在配置文件中,定义的类型,可以和你返回的类型不一样。
	
实际使用工厂Bean
Java代码

在Java该类继承了 FactoryBean 接口之后, 需要实现三个方法,其中getObject 方法, 是返回类的值。

所以,可以在实现接口的时候,给予对应的泛型,即可实现工厂类,最后return出去,在XML文件中创建,

而在Java测试代码中,接收getBean(“myBean”)的数据类型,应该是对应的返回类型,或者大类Object。

package com.w.spring5.factorybean;

import com.w.spring5.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;

public class MyBean implements FactoryBean<Course> {

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }

    /*
    * 定义返回Bean
    * 表示,我设置的MyBean对象,返回的并不是自己本身,
    * 而是我在getObject里返回的对象。Course
    * */
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCourseName("Java课程");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
    <bean id="myBean" class="com.w.spring5.factorybean.MyBean">

    </bean>

/* 测试 */

@Test
public void main2() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");

    Object myBean = context.getBean("myBean");

    System.out.println(myBean);
    // Course{courseName='Java课程'}

}

Bean的作用域

Spring里面,创建创建Bean实例是单实例还是多实例。

在Spring里面,在默认情况下,创建的Bean是一个单实例对象。

如何设置是单实例还是多实例

在spring的配置文件Bean标签里,有一个属性 用于设置是单实例还是多实例

<bean scope=""></bean>
第一个值,默认值, singleton,表示单实例对象
第二个值,prototype,表示多实例对象。

第三个第四个不常用
request
表示一次请求

session
表示一次会话
Bean的生命周期
生命周期:
	一个对象,从你对象创建,到对象销毁的一个过程。

Bean的生命周期:
	(1) 通过构造器去创建Bean的实例(无参数的构造)。
	(2) 如果 Bean 中有属性,就去设置他对应的值。 或者对Bean的一些引用(调用set方法)
	(3) 调用Bean的初始化方法。(需要进行配置)
	(4) Bean可以使用(对象获取到了)
	(5) 当容器关闭的时候,调用Bean销毁的方法。(销毁, 需要自己进行配置销毁。)
演示Bean的生命周期
最基本的类和XML

首先创建一个包,命名为bean,然后在包里创建一个类,命名为 Orders 订单类。

package com.w.spring5.bean;

public class Orders {

    private String ordersName;

    public void setOrdersName(String ordersName) {
        this.ordersName = ordersName;
    }

    @Override
    public String toString() {
        return "Orders{" +
            "ordersName='" + ordersName + '\'' +
            '}';
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation=
       "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="orders" class="com.w.spring5.bean.Orders">
        <property name="ordersName" value="手机" />
    </bean>

</beans>

演示生命周期

第一步是要走类内部的无参构造,为了明显,我们需要在Orders类里添加无参构造

public Orders() {
    System.out.println("第一步, 执行无参构造创建Bean实例");
}

第二步是要调用set方法来进行属性注入,所以我们需要在对应的set方法输出一句话,来证实。

public void setOrdersName(String ordersName) {
    this.ordersName = ordersName;
    System.out.println("第二部, 调用set方法,进行属性注入");
}

第三步,执行默认初始化方法。我们并没有在Java类中对初始化方法进行书写,所以进行书写初始化方法。

这里命名为 initMethod 名称是随便起的,翻译过来就是初始化方法的意思。

// 创建执行的初始化方法
public void initMethod() {
    System.out.println("第三步, 执行初始化的方法。");
}

但是,这里无疑是 一个普通的方法,并不是初始化方法,所以我们需要对该方法进行配置。 在Xml里有一个属性

叫 init-method 对应的值为, 你类里想要的初始化方法的方法名称。

<bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod">
    <property name="ordersName" value="手机" />
</bean>

第四步,获取一个对象。这是最常用到的,也就是ApplicationContext 接口来获取对应的 Bean对象。

@Test
public void ordersTest() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println("第四步,获取Bean容器并且使用");

    System.out.println(orders.toString());

}

第五步,销毁。 在销毁的时候,会执行一个销毁的方法,这个方法需要我们设置,这个销毁对象需要我们手动调用一个方法进行销毁。

首先在Java类中定义销毁后执行的方法。

// 被销毁时执行的方法。
public void destroyMethod() {
    System.out.println("第五步, 执行销毁的方法");
}

在Xml中配置,同上面第三步初始化一样,它也有一个属性,叫 destory-method 对应的值也就是你要执行的方法。

<bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod" 
      destroy-method="destroyMethod">
    <property name="ordersName" value="手机" />
</bean>

在配置之后,他并不会自动销毁,这个只是配置你销毁后所执行的方法。所以需要在Java测试类中手动去配置销毁代码。

有一个方法叫close().

@Test
public void ordersTest() {

    //        ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println("第四步,获取Bean容器并且使用");

    System.out.println(orders.toString());

    // 手动让Bean的实例销毁。
    context.close();
}

把ApplicationContext 替换为 子接口的实现类 ClassPathXmlApplication , close 是 他特有的方法。如果不改,则需要

向下转型,才可以使用close

Application context = new ClassPathXmlApplicationContext("beans4.xml");

((ClassPathXmlApplicationContext) context).close();
最终全部的代码

Java 类

package com.w.spring5.bean;

public class Orders {

    private String ordersName;

    public void setOrdersName(String ordersName) {
        this.ordersName = ordersName;
        System.out.println("第二部, 调用set方法,进行属性注入");
    }

    public Orders() {
        System.out.println("第一步, 执行无参构造创建Bean实例");
    }

    // 创建执行的初始化方法
    public void initMethod() {
        System.out.println("第三步, 执行初始化的方法。");
    }

    // 被销毁时执行的方法。
    public void destroyMethod() {
        System.out.println("第五步, 执行销毁的方法");
    }

    @Override
    public String toString() {
        return "Orders{" +
            "ordersName='" + ordersName + '\'' +
            '}';
    }
}

XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation=
       "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="orders" class="com.w.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="ordersName" value="手机" />
    </bean>

</beans>

Java测试类

@Test
public void ordersTest() {

    //        ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");

    Orders orders = context.getBean("orders", Orders.class);

    System.out.println("第四步,获取Bean容器并且使用");

    System.out.println(orders.toString());

    // 手动让Bean的实例销毁。
    context.close();

}
最终结果
第一步, 执行无参构造创建Bean实例
第二部, 调用set方法,进行属性注入
第三步, 执行初始化的方法。
第四步,获取Bean容器并且使用
Orders{ordersName='手机'}
第五步, 执行销毁的方法
更完整的生命周期
Bean的后置处理 生命周期完整共有七步

在第三步初始化之前,会有一个初始化前方法, 在第三步初始化之后,会有一个初始化后方法。

(1) 通过构造器去创建Bean的实例(无参数的构造)。
(2) 如果 Bean 中有属性,就去设置他对应的值。 或者对Bean的一些引用(调用set方法)

(3) 把Bean的实例,传递给Bean的后置处理器的方法。
(4) 调用Bean的初始化方法。(需要进行配置)
(5) 把Bean的实例,传递给Bean的后置处理器的方法。

(6) Bean可以使用(对象获取到了)
(7) 当容器关闭的时候,调用Bean销毁的方法。(销毁, 需要自己进行配置销毁。)



实现后置处理器

(1) 创建一个类,去实现一个接口( BeanPostProcessor )

在BeanPostProcessor接口里 有两个方法,默认实现

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

而在我们的MyBeanPost里,只需要对这两个方法进行重写

package com.w.spring5.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

也就是在初始化之前,会执行 postProcessBeforeInitialization 这个方法

在初始化之后,会执行 postProcessAfterInitialization 这个方法

为了更好的区分,我们在初始化前的 postProcessBeforeInitialization 方法里加上一个输出语句,初始化后的 postProcessAfterInitialization 也一样加上一个输出语句

package com.w.spring5.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 在执行前输出一句话,为了更好的区分
        System.out.println("初始化执行前的后置处理器");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 在执行后输出一句话,为了更好的区分
        System.out.println("初始化执行后的后置处理器");
        return bean;
    }
}

其实他就是一个普通类,所以我们需要在XML配置文件中配置这个类,让他可以实际用起来.

<!-- 配置后置处理器-->
<bean id="myBeanPost" class="com.w.spring5.bean.MyBeanPost" />

这是一个新建的Bean,不是内部Bean,新建的myBeanPost这个Bean,在创建的时候,发现 MyBeanPost这个类,实现了 BeanPostProcessor 这个接口,呢么Spring就会认为他是一个后置处理器,他会帮你把 所有的Bean都自动的添加上,后置处理器这个功能,并且执行里面的方法。

最终效果

Java测试代码不变

第一步, 执行无参构造创建Bean实例
第二部, 调用set方法,进行属性注入
初始化执行前的后置处理器
第三步, 执行初始化的方法。
初始化执行后的后置处理器
第四步,获取Bean容器并且使用
Orders{ordersName='手机'}
第五步, 执行销毁的方法

对比于之前的五步,我们无非发现,在第三步的前后,分别加上了一个执行前的方法和一个执行后的方法。这就是后置处理器要做的事情。

XML自动装配
什么是自动装配

根据指定规则装配,(属性类型或者属性名称),Spring 自动将匹配的属性值进行注入,这个过程就叫做自动装配。

说白了就是简化开发。

手动装配演示

首先新建一个包,起名为 autowire, 在包里新建类, Emp代表员工,Dept代表部门。

因为一个员工只有一个部门,而一个部门有多个员工,所以类的设计为下

package com.w.spring5.autowire;

public class Dept {
    
    @Override
    public String toString() {
        return "Dept{}";
    }
    
}
package com.w.spring5.autowire;

public class Emp {
    
     private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "dept=" + dept +
                '}';
    }
}

XML配置里装配Bean对象。 这一步是烂熟于心的。

<bean id="emp" class="com.w.spring5.autowire.Emp">
    <!-- 注入dept部门类-->
    <property name="dept" ref="dept" />
</bean>

<bean id="dept" class="com.w.spring5.autowire.Dept">

</bean>

Java测试类

@Test
public void empAndDept() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans5.xml");

    Emp emp = context.getBean("emp", Emp.class);

    System.out.println(emp.toString());
    // Emp{dept=Dept{}}
}

自动装配

呢么自动装配,就不需要在Bean里去写property了

在Bean标签里有一个属性叫, autowire 他有两个对应且常用的值, byName,byType

byName: 根据属性名称注入对应的值。
byType: 根据属性类型注入对应的值。
ByName
<bean id="emp" class="com.w.spring5.autowire.Emp" autowire="byName">
    <!-- 实现自动装配-->
    <!--
            bean标签 有一个属性叫 autowire
            比较常用的有两个值,byName,byType
            byName: 根据属性名称注入。
            byType: 根据属性类型注入。
        -->
</bean>

<bean id="dept" class="com.w.spring5.autowire.Dept">

</bean>

只需要这样书写,就可以实现 根据名称自动注入属性的写法。

注意点:

类里面的属性 名称,要和 注入的id完全一致

打个比方

package com.w.spring5.autowire;

public class Emp {
    
     private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }
}

拿该类举例, private Dept dept。 呢么对应的名称是 dept,要想根据名称注入属性,呢么 所对应的Bean对象的Id,就必须和该属性的名称完全一致。

<bean id="dept" class="xxxxx"></bean>

这样在装配了autowire之后,才能完成自动装配。

<bean id="emp" class="xxxxx" autowire="byName">
</bean>
<bean id="dept" class="xxxxx"></bean>

最后在Java代码测试类中进行测试

// Emp{dept=Dept{}}
与手动装配完全一致。所以为自动装配成功。
byType

在根据 byName的基础上,进行更改。

<bean id="emp" class="xxxxx" autowire="byType">
</bean>
<bean id="dept" class="xxxxx"></bean>

这是唯一的。如果你根据类型创建,而 对应类型的 dept却有两个,如下。

<bean id="emp" class="xxxxx" autowire="byType">
</bean>
<bean id="dept" class="xxxxx"></bean>
<bean id="dept" class="xxxxx"></bean>

因为他是根据类型做匹配,你出现多个,他并不知道匹配哪一个,所以报错。要注意这个问题。

(4) IOC操作Bean管理(外部属性文件)

实际用处

在开发中,我们在一个XML配置文件里,可能会配置很多的类,很多的东西,但是有一些写死的东西,固定的东西, 有可能会去重复的书写,比如JDBC操作数据库的数据库驱动链接。 数据库地址,账号密码,端口号等等一些基本上是写死的东西,我们就可以把它写到一个properties文件里,然后在XML文件中去读取这个文件的一个过程,这就是读取外部属性文件。

实际操作(以数据库为例)
配置德鲁伊连接池(直接方式)

首先肯定需要引入德鲁伊连接池的依赖 或者 jar包

然后要去XML配置文件里创建德鲁伊的相关Bean对象,和对应的属性注入。

在连接池中有一些最基本的属性

<!-- 直接配置连接池-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="" />
    <property name="url" value="" />
    <property name="username" value="" />
    <property name="password" value="" />
</bean>
<!-- 直接配置连接池-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</bean>

在配置之后,发现,这些值都是固定死的,呢么做修改的时候,或者下次使用的时候,需要直接拷贝,很麻烦,呢么我们可以写到一个外部配置文件里。

所以有了下一步,引入外部的文件方式

配置德鲁伊(引入文件方式)

(1)创建外部是属性文件。properties格式的文件。然后在里面配置数据库的信息。

prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/mydb
prop.username=root
prop.password=root

(2)把外部 properties 文件引入到spring的配置文件中

  • 引入名称空间,context

  • <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation=
                   "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    </beans>
    
  • 在spring的配置文件中,使用标签引入外部配置文件

  • <!-- 引入外部的属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />
    
  • 配置连接池

  • <!-- 配置连接池-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}" />
        <property name="url" value="${prop.url}" />
        <property name="username" value="${prop.username}" />
        <property name="password" value="${prop.password}" />
    </bean>
    
    <!-- 引入外部的属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />
    
    

(5) IOC操作Bean管理 基于注解

什么是注解

注解是你代码里一些特殊的标记

格式: @注解名称(属性名称=属性值,…)

注解可以作用在类上,或者方法上面,属性上面。

使用的目的:为了简化XML的开发,更优雅的代码。

使用注解创建对象(Bean)

Spring 针对 Bean 管理中创建对象提供的注解

 - @Component
 - @Service
 - @Controller
 - @Repository

我们上面的四个注解,他们的功能是一样的,都可以用来创建你的Bean实例。

只是用在了不同的构架上面,用错了也无所谓。只是为了让开发人员更加清晰当前组件所扮演的角色。

基于注解的方式创建对象

第一步 引入依赖

需要引入AOP的依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-97OP2UwB-1624278479572)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624268396206.png)]

到目前为止,所引入到项目的jar包有以下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qDdOuetA-1624278479577)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624268480276.png)]

第二步 开启组件扫描

在XML配置文件中引入 context的命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation=
       "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解扫描-->
    <context:component-scan base-package="com.w.spring5" />

    <!-- 如果要扫描多个指定的包,可以使用逗号隔开-->
	<context:component-scan base-package="com.w.spring5, com.w.spring4, com.w.spring3" />
</beans>

开启注解扫描需要使用到 context:component-scan 这个标签, 有一个属性 base-package 里面是要扫描的包下的路径。

第三步 创建类,在类上面添加注解

在spring5的包下,创建三个包,dao,service,textdemo

spring5并非指定的,而是他们的上一级目录,这个名字可以随便起,我在类路径下的包全路径为:

com.w.spring5.dao

com.w.spring5.service

com.w.spring5.textdemo

在service包下,创建UserService类,并且创建方法 add() 打印输出一句话,并且加上@Service注解

package com.w.spring5.service;

import org.springframework.stereotype.Service;

@Service(value="userService")
public class UserService {

    public void add() {
        System.out.println("add执行了...");
    }

}

在注解里可以有一个值,value, value也可以不写,如果不写的话,默认值就是你的类名称,把类名称的首字母小写,其他不变

UserService ----> userService

@Service(value="userService")

呢么就等同于xml配置bean里面的 id属性,相当于起别名。

<bean id="userService" class="xxx"></bean>

最后去进行测试

package com.w.spring5.testdemo;

import com.w.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void userServiceTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        UserService userService = context.getBean("userService", UserService.class);

        userService.add();	// add执行了...
    }

}

也就代表了被@Service 托管的Spring对象,是可以获取到的。

总结注解的方式创建对象

首先,我们需要导入一个依赖,AOP的jar包,在导入之后,因为Spring启动扫描是需要 context命名空间,所以我们需要在xml文件中引入对应的命名空间,之后,开启spring的扫描,去扫描对应包下的被Spring托管的对象。然后我们需要在需要被托管的对象的类名称上,加上对应的注解,@Service等注解。看情况加,之后就可以去通过Java测试类去获取到该Bean对象。

组件扫描细节配置

在第一步的导入jar包,和第二步导入对应命名空间之后, 有一个组件扫描,这里我们会详细去说一下组件扫描的一些细节配置,我们可以约束那些类哪些配置可以不进行扫描,哪一些去扫描等等。

举两个例子,示例一:

<context:component-scan base-package="com.wang" use-default-filters="false">
	<context:include-filter type="annotation"
                            expression="org.springframework.sterertype.Controller" />
</context:component-scan>

可以看到有一个新的属性,

use-default-filters=“false” 表示,现在不使用默认的filters,使用自己配置的filters

context:include-filter 通过 这个,设置扫描哪一些内容。

type=annotation, 表示根据注解来进行扫描。

expression=“org.springframework.sterertype.Controller”,表示扫描的话,只扫描带 @Controller 注解的扫描

示例二:

<context:component-scan base-package="com.wang">
	<context:exclude-filter type="annotation"
                            expression="org.springframework.sterertype.Controller" />
</context:component-scan>

context:exclude-filter 是不包含的意思,表示不包含 @Controller注解的标识的类。 也就是不去扫描被@Controller标注的类。

基于注解实现属性的注入(了解)

基于注解实现的属性注入, 这是四个最常用的注解, 但是这前三个类型都是基于对象属性去注入的,若要注入普通类型,如String,int等。则可以使用@Value() 后续会说。

(1) @Autowired

根据属性类型,进行自动装配。

(2) @Qualifier

根据属性名称,进行注入。

(3) @Resource

可以根据类型注入,也可以根据名称注入。

(4) @Value

注入普通类型。

@Autowired

把service 和 dao 层的对象创建,在service 和 dao 类添加创建对象注解。

在UserService类里,定义UserDao类型的属性。可以通过接口new实现类,多态的方式实现。在属性上方使用我们的注解就可以做到。 不需要添加 set 方法。这一步已经被封装过了。

添加注解。

书写测试类测试 UserDaoImpl的add方法。

/* UserService */
package com.w.spring5.service;

import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service(value="userService")
public class UserService {

    // 创建UserDao类型的属性
    @Autowired
    private UserDao userDao;

    public void add() {
        System.out.println("add执行了");
        userDao.add();
    }

}

/* UserDao --- interface */
package com.w.spring5.dao;

public interface UserDao {

    void add();

}
/* UserDaoImpl */
package com.w.spring5.dao;

import org.springframework.stereotype.Repository;

// UserDao的实现类
@Repository
public class UserDaoImpl implements UserDao {

    @Override
    public void add() {

        System.out.println("UserDaoImpl add.......");

    }

}
/* JavaTest */
@Test
public void UserDaoImpl() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    UserService userService = context.getBean("userService", UserService.class);

    userService.add();
    /*
    	add执行了
		UserDaoImpl add.......	
    */

}
@Qualifier

这个@Qualifier注解的使用,和上面的@Autowired要在一起进行使用。

因为一个接口可以有多个实现类,在一个接口只有一个实现类的时候,我们的@Autowired可以根据类型,去自动注入对应的属性。但是一旦有多个实现类,他不知道注入哪个,会产生错误,这个时候就可以用到我们的@Qualifier,通过名称注入了。这样可以指定,唯一,不会产生莫名的错误。

在原有代码的基础上,我们在UserDaoImpl这个实现类上的类注解,@Repository 里,加上 value属性,给他指定id名称。

import org.springframework.stereotype.Repository;

// UserDao的实现类
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao {

    @Override
    public void add() {

        System.out.println("UserDaoImpl add.......");

    }

}

然后在UserService类里,通过名称给他注入属性。 只需要加上@Qualifier(value = “” ) ,value对应的值,是你要注入类型的,并且在Spring容器中存在的,id名称。也可以说是别名。

package com.w.spring5.service;

import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service(value="userService")
public class UserService {

    // 创建UserDao类型的属性
    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;

    public void add() {
        System.out.println("add执行了");
        userDao.add();
    }

}

在其余结构不变的情况下,去执行我们原有的Java测试代码。得到的结果如下

add执行了
UserDaoImpl add.......

可以看到,依然是可以被注入的。

@Resource

若要使用Resource 则需要导入包

import javax.annotation.Resource;

如果要根据类型注入,直接加上Resource,

若是根据名称注入,@Resource(name = “”) name对应的属性为 你要注入的名称 id。

@Value

在基础属性上加上@Value(“”) 属性为对应的值。

@Value("张三")
private String name;

@Value(value = "张三")
是一个效果

// 此时, name为张三。
完全注解开发
创建配置类,代替xml文件!

在spring5下,创建一个包,叫config。这里面存放我们的配置类,配置类名称为 SpringConfig。当然。这个名称可以随便起。

你只有一个完整的类,Spring肯定不会去认识你这个类,所以,我们需要加上一个注解,@Configuration

这样他会把你当成一个Spring的配置类。

然后,使用@ComponentScan(basePackages = {“com.w.spring5”}) 来表示要扫描的包路径, 里面对应的属性是一个对象类型,表明了包类路径。

package com.w.spring5.config;

import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"com.w.spring5"})
public class SpringConfig {
    
}

这个时候,我们就可以把beans.xml 进行一个删除的操作

删除之后,我们的测试类代码,就会跟之前有一点点的不一样。

ApplicationContext 不变, 但是 new 的对象变成了,AnnotationConfigApplicationContext

Annotation 是注解的意思,他让你在内部去传入一个配置类,所以我们通过 SpringConfig.class 反射的方式,

把我们写的配置类传给他,剩下的几步不变。

import com.w.spring5.config.SpringConfig;
import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {

    @Test
    public void userDaoImpl() {

        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        UserService userService = context.getBean("userService", UserService.class);

        userService.add();

        System.out.println(userService.toString());
        /*
        UserService add....
        UserDaoImpl add.....
        UserService{userDao=com.w.spring5.dao.UserDaoImpl@4c012563, name='张三'}
        * */

    }

}


4.AOP(面向切面)

(1)Aop的基本概念

什么是AOP

(1) 面向切面编程(面向方面编程)  是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

(2) 口述AOP案例及场景

有一个登录的场景,需要我们去做。在前端提交的form表单,获取到账号密码之后,我们读取数据库,然后判断是否存在对应的账号密码,如果成功了,呢么我们就定向到登陆成功的页面,反之,让他重新输入账号密码。

突然有一天,老板让你去增加一个功能, 增加一组登录日志功能,在不改变原有代码的基础上,去新建一个模块,这个模块专门管理登录日志这个功能,然后通过AOP面向切面的方式给他添加到代码里,这样,我们没有对原有的代码进行改动,也实现了新功能,假设有一天不需要这个功能了,我们只需要撤出对应的AOP配置,呢么依然不需要对原有的代码进行一点的改动。这就是AOP的使用场景。

(3)AOP-底层原理

AOP的底层,使用到了动态代理方式。

有两种情况的动态代理。

第一种情况。 有接口的情况下使用JDK动态代理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeOg3Sew-1624359838168)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624348347593.png)]

第二种情况,没有接口的情况下使用CGLIB动态代理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A51ibWUy-1624359838170)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624348570009.png)]

(4) 通过代码的方式实现JDK动态代理

1,使用 JDK 动态代理,使用 Proxy类里的方法,来创建出代理对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IyuFsjTg-1624359838172)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624349229359.png)]

在Proxy类里面有一个方法叫 newProxyInstance(ClassLoader loader, <?>[] interfaces, InvocationHandler h)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sBBOQkoU-1624359838174)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624349363068.png)]

这个方法里有三个参数, loader, interfaces, h

第一个参数,得到当前的类加载器。

第二个参数,增强方法所在的类,这个类,实现的呢个接口。 支持多个接口。

第三个参数,实现这个接口,InvocationHandler,创建代理对象。写我们增强的方法。

下面使用代码来编写这些东西

(1)创建接口,定义方法

package com.w.spring5;

public interface UserDao {

    int add(int a, int b);

    String update(String id);

}

(2)创建接口实现类,实现方法

package com.w.spring5;

public class UserDaoImpl implements UserDao {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public String update(String id) {
        return id;
    }

}

(3)使用Proxy类创建一个接口的代理对象

package com.w.spring5;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JdkProxy {

    public static void main(String[] args) {

        UserDao userDao = new UserDaoImpl();

        Class[] interfaces = {UserDao.class};

        // 创建接口实现类的代理对象。
        UserDao userDao1 = (UserDao) Proxy.newProxyInstance(
                JdkProxy.class.getClassLoader(),
                interfaces,
                new UserDaoProxy(userDao)
        );

        userDao1.add(1, 2);

    }

}

// 创建代理对象的代码
class UserDaoProxy implements InvocationHandler {

    // 把创建的是谁的代理对象,把谁给他传递过来。
    // 通过有参构造传递。
    private Object object;
    public UserDaoProxy(Object o) {
        this.object = o;
    }

    // 增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 方法前
        System.out.println("方法之前执行...." + method.getName() + " :传递的参数 " +
                Arrays.toString(args));

        // 被增强的方法
        Object invoke = method.invoke(object, args);

        // 方法后
        System.out.println("方法之后执行...." + object);

        return invoke;
    }
}

一步一步解析一下,首先在接口和实现类,创建好之后,我们写了一个代理类,JdkProxy

通过 newProxyInstance 这个方法,来实现接口的代理对象。为了更加灵活,我们新建一个类

UserDaoProxy, 让他实现InvocationHandler, 然后我们通过有参构造器的方式,给他把传进来的对象赋值给属性。

然后写对应的增强逻辑,重写InvocationHandler 的 invoke 方法, 然后把该方法返回。

然后

UserDao userDao1 = (UserDao) Proxy.newProxyInstance(
JdkProxy.class.getClassLoader(),
interfaces,
new UserDaoProxy(userDao)
);

通过这种方式,强转为UserDao类型。调用其对应的方法。

(5)AOP-操作术语

(1) 连接点

类里面的那些方法可以被增强,呢么这些方法就叫做连接点。

(2) 切入点

实际被真正增强的方法,他就被称为切入点。

(3) 通知(增强)

实际增强的逻辑部分,成为通知(增强)

有多种通知类型:

  • 前置通知

    前置通知是什么,比如现在想增强User类里的add()方法,呢么就表示,在add()执行前,会执行这个前置通知。

  • 后置通知

    后置通知和前置通知一样,只是把通知的位置翻了过来,会在执行后去执行这个后置通知。

  • 环绕通知

    环绕通知相当于前置通知和后置通知的结合体,会在前和后分别去进行一个通知。

  • 异常通知

    现在想增强一下User的add()方法,当add方法出现异常的时候,他就会执行。

  • 最终通知

    就类似于我们Java代码的 try catch - finally 中的 finally,不管怎么样,他最终都会执行。

(4) 切面

切面他做了一个动作,把我们的通知应用到切入点的一个过程,他就叫切面

(6)AOP操作-准备工作

  • Spring框架一般基于 AspectJ 实现 针对AOP的一些操作。

    • 什么是 AspectJ。
      • 它本身并不是Spring的组成部分,他是一个独立的AOP的框架,它不需要Spring也可以单独使用。
      • 一般我们会把AspectJ 和 Spring框架的组合使用,来进行AOP的相关操作。
  • 基于 AspectJ 实现AOP操作。

    • 第一种: 基于XML配置文件方式的形式实现
    • 第二种:基于注解方式实现。(一般使用注解方式)
  • 在项目工厂里引入AOP相关的一些依赖。

    • spring-aspects-5.2.0.RELEASE.jar

    • com.springsource.net.sf.cglib-2.2.0.jar

    • 以及jar包不好找,俩maven依赖

      • <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        
  • 切入点表达式

    • 切入点表达式的作用
      • 知道对哪个类,里面的哪个方法,进行增强。
      • 如何去书写表达式的语法结构。
        • execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )

举例一: 对 com.w.dao.BookDao 类 里面的add方法进行增强。

execution(* com.w.dao.BookDao.add(..));

第一个 * 号表示,可以是任意的权限修饰符,权限修饰符是可以省略不写的,而方法返回值类型必须去写(在此感谢 @斯巴鲁冲冲冲 提供的错误。)。然后后面跟类全路径,在往后可以在类全路径后面直接通过 . 来add 调用该方法,然后在方法后面跟小括号,() 里面的 … 代表了方法中的参数。

举例2: 我想对com.w.dao.BookDao 类里面的所有方法进行增强。

只需要把ADD变为 *

execution(* com.w.dao.BookDao.*(..));

举例3: 我想对com.w.dao包里面的所有类,类里面的所有方法都进行增强。

execution(* com.w.dao.*.*(..));

(7)AOP操作-AspectJ基于注解(1)

  • 创建一个类,类里面定义一个方法。实现类中的方法增强。

  • package com.w.spring5.aopano;
    
    public class User {
    
        public void add() {
            System.out.println("add...");
        }
    
    }
    
  • 创建一个增强类(编写你增强的逻辑)

    • 在增强类里面,创建方法,让不同的方法代表不同的通知类型。

    • package com.w.spring5.aopano;
      
      public class UserProxy {
      
          public void before() {
              System.out.println("before......");
          }
      
      }
      

User类是一个被增强的类。

UserProxy是一个增强类。 在UserProxy方法里有一个before方法。

需求就是在add方法之前,把before方法做一个执行。 还记得我们前面说的前置围绕吗。

  • 进行通知的配置

    • 因为我们是基于注解做到的。所以我们现在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"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation=
             "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
      
          <!-- 开启注解扫描-->
          <context:component-scan base-package="com.w.spring5" />
      
      </beans>
      
    • 使用注解来创建User 和 UserProxy对象。

    • package com.w.spring5.aopano;
      
      import org.springframework.stereotype.Controller;
      
      @Controller
      public class UserProxy {
      
          public void before() {
              System.out.println("before......");
          }
      
      }
      
    • package com.w.spring5.aopano;
      
      import org.springframework.stereotype.Controller;
      
      @Controller
      public class User {
      
          public void add() {
              System.out.println("add...");
          }
      
      }
      
    • 在增强的类上面添加一个注解 @Aspect

    • package com.w.spring5.aopano;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.stereotype.Controller;
      
      @Controller
      @Aspect  // 生成代理对象。
      public class UserProxy {
      
          public void before() {
              System.out.println("before......");
          }
      
      }
      
      
    • 在Spring的配置文件中开启生成代理对象

      • 在这个过程中,还需要用到一个命名空间,这个命名空间叫做AOP,所以需要引入对应的命名空间。

      • <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:context="http://www.springframework.org/schema/context"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation=
               "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        
            <!-- 开启注解扫描-->
            <context:component-scan base-package="com.w.spring5" />
        
        </beans>
        
      • <!-- 开启Aspect生成代理对象-->
        <aop:aspectj-autoproxy />
        
    • 配置不同类型的通知,这里是前置围绕

      • 在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置。

        • package com.w.spring5.aopano;
          
          import org.aspectj.lang.annotation.Aspect;
          import org.aspectj.lang.annotation.Before;
          import org.springframework.stereotype.Controller;
          
          @Controller
          @Aspect // 生成代理对象
          public class UserProxy {
          
              @Before("execution(* com.w.spring5.aopano.User.add(..))")
              public void before() {
                  System.out.println("before......");
              }
          
          }
          
          

在增强的方法上面,添加@Before前置环绕注解,然后里面有一个value值,这个value可写可不写,对应的值是 切入点表达式。

进行Java测试类代码的书写

import com.w.spring5.aopano.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void aopTestAno() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        User user = context.getBean("user", User.class);

        user.add();
        /*
        before......
        add...
        * */

    }

}

可以看到前置环绕以及成功的添加。现在捋一下步骤。

第一步,我们首先创建了一个被增强的类。User类。 在User类里面有一个需要被增强的方法,add方法。

第二步,我们又创建了一个增强类,UserProxy类,里面有对应的增强逻辑。

第三步,在这两个类上,标注上Spring的注解,让他被Spring托管。创建对应的对象。

第四步,我们需要在XML配置文件里,开启对应的注解扫描。

第五步,引入代理对象创建所需的命名空间,aop。 并且开启代理对象的创建。在XML里。

第六步,在增强类里,标注上@Aspect注解,表示生成代理对象。然后在需要向被增强类User类里添加的方法上标注@Before() 在其里添加上对应的表达式。

第七步,书写测试类进行测试。

需要注意的是,我们在测试的时候,获取的类,是被增强的类。拿上面举例子,也就是我们的User类。

但是别忘了,不光前置环绕一种通知。共有五种通知类型。呢么下面在原有代码的基础上去补其他几种。

// 前置环绕
@Before("execution(* com.w.spring5.aopano.User.add(..))")

// 后置环绕也可以叫做最终通知
@After("execution(* com.w.spring5.aopano.User.add(..))")

// 环绕通知
@Around("execution(* com.w.spring5.aopano.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("环绕前......");

    // 被执行的方法
    proceedingJoinPoint.proceed();

    System.out.println("环绕后......");
}

// 后置通知/返回通知
@AfterReturning("execution(* com.w.spring5.aopano.User.add(..))")

// 在方法抛出异常后执行
@AfterThrowing("execution(* com.w.spring5.aopano.User.add(..))")

完整的Java代码

package com.w.spring5.aopano;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Controller;

@Controller
@Aspect // 生成代理对象
public class UserProxy {

    @Before("execution(* com.w.spring5.aopano.User.add(..))")
    public void before() {
        System.out.println("before......");
    }

    @After("execution(* com.w.spring5.aopano.User.add(..))")
    public void after() {
        System.out.println("after......");
    }

    @AfterReturning("execution(* com.w.spring5.aopano.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning......");
    }

    @AfterThrowing("execution(* com.w.spring5.aopano.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing......");
    }

    // 环绕通知
    @Around("execution(* com.w.spring5.aopano.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前......");

        // 被执行的方法
        proceedingJoinPoint.proceed();

        System.out.println("环绕后......");
    }

}

侧重于异常来讲解一下,我们在add方法中,手动给他弄出一个异常, 比如拿一个整数 去 除以 0;

package com.w.spring5.aopano;

import org.springframework.stereotype.Controller;

@Controller
public class User {

    public void add() {
        int i = 10 / 0;
        System.out.println("add...");
    }

}

然后注意测试类的结果。

毋庸置疑,他会给我们抛出一个异常

java.lang.ArithmeticException: / by zero

但是这不是重点,因为这个异常是我们自己弄出来的,我们清楚,重点是看执行的内容。

环绕前......
before......
after......
afterThrowing......

可以注意到,他没有执行add方法,和add方法后应该坏绕的结果。 但是他在add方法前,环绕通知和异常通知执行了,但是终止在了异常通知部分。 after依然会执行。

(8)AOP操作-AspectJ基于注解(2)

重用切入点的定义

相同的切入点进行抽取。

在上面的例子中,我们所有的切入点表达式,都是重复单一的。这样写,若是以后有改动,呢么所有的表达式都需要进行改动,非常麻烦,所以我们把切入点抽取出来

// 相同的切入点进行抽取
@Pointcut(value = "execution(* com.w.spring5.aopano.User.add(..))")
public void pointDemo() {

}

定义一个方法,在上面标注@Pointcut,后面写上重复使用的表达式。然后我们就可以在下次使用切入点表达式的时候,去直接调用该方法( 需要加上方法括号 )。如:

@Before("pointDemo()")
public void before() {
    System.out.println("before......");
}

@After("pointDemo()")
public void after() {
    System.out.println("after......");
}

这样写的好处毋庸置疑,在下次需要改动的时候,我们只需要对pointDemo上@Pointcut里的表达式进行更改,就可以使得下面的表达式生效。更加方便了我们代码的书写。

多个增强类对同一个方法进行增强,可以设置增强类的优先级

我们定义一个类,PersonProxy,这是一个增强类。

package com.w.spring5.aopano;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class PersonProxy {

    // 前置通知
    @Before("execution(* com.w.spring5.aopano.User.add(..))")
    public void before() {
        System.out.println("PersonBefore......");
    }
}

他做了一个前置通知的效果,但是别忘了。我们在这个增强类的同时,还有一个增强类,UserProxy。

我们假设要做到一个优先级的顺序,怎么做。

首先,我们在增强类的上面标注一个注解,@Order([值]),这个值,是数字类型的值,这个数字的值越小,呢么他的优先级就越高。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;

@Controller
@Aspect // 生成代理对象
@Order(1)
public class UserProxy {

    // 相同的切入点进行抽取
    @Pointcut(value = "execution(* com.w.spring5.aopano.User.add(..))")
    public void pointDemo() {

    }

    @Before("pointDemo()")
    public void before() {
        System.out.println("before......");
    }

    @After("pointDemo()")
    public void after() {
        System.out.println("after......");
    }

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

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

    // 环绕通知
    @Around("pointDemo()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕前......");

        // 被执行的方法
        proceedingJoinPoint.proceed();

        System.out.println("环绕后......");
    }

}
package com.w.spring5.aopano;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(2)
public class PersonProxy {

    // 前置通知
    @Before("execution(* com.w.spring5.aopano.User.add(..))")
    public void before() {
        System.out.println("PersonBefore......");
    }
}

呢么,如果按照上面的方式来进行增强,我们执行的顺序,应该是先去实现 UserProxy,然后才是PersonProxy。

我们执行测试代码得到结果:

环绕前......
before......
PersonBefore......
add...
环绕后......
after......
afterReturning......

看起来确实如此,PersonBefore在 before后面执行了。

我们把Order里对应的值的顺序反过来。然后执行测试代码。得到的结果是:

PersonBefore......
环绕前......
before......
add...
环绕后......
after......
afterReturning......

可以明确的看到,PersonBefore , 在前面执行了。

(9)AOP操作-AspectJ-XML配置文件实现(非重点)

(1)创建两个类,增强类和被增强类。创建对应的add方法。

package com.w.spring5.aopano;

public class UserProxy {

    public void before() {

        System.out.println("User Proxy方法before执行了......");

    }

}
package com.w.spring5.aopano;

public class User {

    public void add() {

        System.out.println("User Add方法执行了......");

    }

}

(2)在配置文件中创建两个对象。

<!-- 创建两个类的对象-->
<bean id="user" class="com.w.spring5.aopano.User">

</bean>

<bean id="userProxy" class="com.w.spring5.aopano.UserProxy">

</bean>

(3)在spring配置文件中配置切入点

<!-- 配置aop增强 -->
<aop:config>
    <!-- 配置切入点-->
    <aop:pointcut id="p" expression="execution(* com.w.spring5.aopano.User.add(..))"/>
    <!-- 配置切面-->
    <aop:aspect ref="userProxy">
        <!-- 配置你的增强方法具体作用在哪里-->
        <aop:before method="before" pointcut-ref="p" />
    </aop:aspect>
</aop:config>

拆分讲解,首先我们有一个标签叫 aop:config 表示了一个aop的配置,

然后我们配置一个切入点,还记得AOP-操作术语吗。实际被真正增强的方法,他就被称为切入点

呢么后面的表达式,就是你要实际加强的方法,id 相当于起别名。

然后我们需要配置一个切面,实际增强的逻辑部分,成为通知(增强) 告诉他你的增强类是哪一个,然后里面有一个

标签叫做 aop:before,这就是你的前置环绕, 两个参数, method 对应你类里的增强方法, pointcut-ref 表示你要被增强类的方法是什么。

最终我们进行测试

@Test
public void myTest() {

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    User user = context.getBean("user", User.class);

    user.add();

    /*
        User Proxy方法before执行了......
        User Add方法执行了......
        * */

}

可以看到,他已经进行了前置环绕。

如果其他的环绕,其实也是大同小异的

我们只需要更改xml配置

<!-- 配置aop增强 -->
<aop:config>
    <!-- 配置切入点-->
    <aop:pointcut id="p" expression="execution(* com.w.spring5.aopano.User.add(..))"/>
    <!-- 配置切面-->
    <aop:aspect ref="userProxy">
        <!-- 配置你的增强方法具体作用在哪里-->
        <aop:before method="before" pointcut-ref="p" />
    </aop:aspect>
</aop:config>

也就是更改
<!-- 配置你的增强方法具体作用在哪里-->
<aop:before method="before" pointcut-ref="p" />
这一行的配置
aop:after
最终环绕。等等。以此类推

(10) 完全注解开发

package com.w.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"com.w.spring5"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {

}

首先我们要让Spring知道这是一个配置类肯定是没错的,而且还要开启直接扫描。

@EnableAspectJAutoProxy(proxyTargetClass = true) 是开启代理对象。代替掉了XML的

<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy />

这一句话,它里面的值,如果不写的情况下,默认为 false,所以我们需要给他打开,让他生成代理对象。

这样我们就不需要XML配置文件了。

(11)完结总结

在AOP完结后,我们需要重点掌握的一些内容:

  • AOP的概念。
  • AOP底层原理(动态代理,有接口和没接口的情况)
  • AOP术语(连接点,接入点,通知,切面)
  • 注解方式开发(重点)
  • 配置文件开发。
  • 完全注解开发

5.至目前为止所有的jar包和依赖

  • com.springsource.net.sf.cglib-2.2.0.jar

  • commons-logging-1.2.jar

  • druid-1.1.22.jar

  • mysql-connector-java-5.1.49.jar

  • spring-aop-5.2.0.RELEASE.jar

  • spring-aspects-5.2.0.RELEASE.jar

  • spring-beans-5.3.8.jar

  • spring-context-5.3.8.jar

  • spring-core-5.3.8.jar

  • spring-expression-5.3.8.jar

  • <dependencies>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.2</version>
        </dependency>
    
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>
    
        <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
    
    </dependencies>
    

6. JdbcTemplate

(1) 概念和准备工作

什么是JdbcTemplate

  • Spring对Jdbc 进行了封装。使用JdbcTemplate可以很方便的实现对数据库的增删改操作。

准备工作

(1) 引入相关的Jar包。

  • mysql-connector-java-5.1.49.jar
  • druid-1.1.22.jar
  • spring-tx-5.2.0.RELEASE.jar

(2) 创建数据库user_db / 在spring配置文件中配置数据库连接池

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql:///user_db" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</bean>

(3) 配置JdbcTemplate对象,对象注入DataSource

<!-- 创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 注入DataSource-->
    <property name="dataSource" ref="dataSource" />
</bean>_

(4) 创建Service类,创建Dao类,在dao注入jdbcTemplate对象。

首先创建service 和 dao 包。

然后创建对应的对象。首先要有Dao接口 和 Impl实现类

package com.w.spring5.dao;

public interface BookDao {
}
package com.w.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {

    // 注入JDBCTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

}

我们在Dao实现类里,注入了JdbcTemplate。

然后我们在Service调Dao层的实现类

package com.w.spring5.service;

import com.w.spring5.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("bookService")
public class BookService {

    // 注入Dao
    @Autowired
    private BookDao bookDao;

}

(2) 对数据库的添加功能

在数据库里新建一个表,t_book 有三个字段,user_id, username, ustatus. 用户id ,用户名称, 和用户状态。

分别对应 int varchar varchar.

一般一个表会对应一个实体类,我们来进行实体类的书写。

(1) 对应数据库,创建实体类。

我们新建一个包,entity,然后新建一个类 Book

package com.w.spring5.entity;

public class Book	 {

    private String userId;

    private String userName;

    private String uStatus;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getuStatus() {
        return uStatus;
    }

    public void setuStatus(String uStatus) {
        this.uStatus = uStatus;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", userName='" + userName + '\'' +
                ", uStatus='" + uStatus + '\'' +
                '}';
    }
}

(2)编写service 和dao

在dao里面进行数据库添加的操作。

首先我们要写一个对应的接口,接口有一个参数,类型为:Book类型。为了方便们传值。

package com.w.spring5.dao;

import com.w.spring5.entity.Book;

public interface BookDao {
    void add(Book book);
}

然后我们编写对应的 BookDaoImpl 类。

我们让这个类去实现BookDao这个接口,并且重写add方法。在方法里,我们可以使用提前注入好的 jdbcTemplate 属性。调用一个方法,update。 update有两个参数,第一个是String 类型的 sql语句,第二个 是一个不定长参数。

package com.w.spring5.dao;

import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {

    // 注入JDBCTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void add(Book book) {
        // sql语句
        String sql = "insert into t_book values(?, ?, ?)";

        Object[] args = {book.getUserId(), book.getUserName(), book.getuStatus()};
        // 调用方法实现
        int update = jdbcTemplate.update(sql, args);

        System.out.println("更新成功,最终添加了: " + update + " 记录");
    }
}

jdbcTemplate.update(String sql, Object… args);

第二个参数是一个不定长参数,我们可以通过上面的代码看到 我们的sql语句,String sql = “insert into t_book values(?, ?, ?)”

其中,在values里,有三个 ? , 表示了占位符,每一个问号,对应一个值,呢么我们就可以在 args 这个参数里实现,有几个问号,写几个值,按顺序来写。

我们在BookService里,添加一个add方法,让他去调用自动注入的bookDao.add方法,并且传入一个(book) 类型的对象。

package com.w.spring5.service;

import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("bookService")
public class BookService {

    // 注入Dao
    @Autowired
    private BookDao bookDao;

    // 添加的方法
    public void addBook(Book book) {

        bookDao.add(book);

    }

}

最终去写测试代码。

import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTestBook {

    @Test
    public void testBook() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        BookService bookService = context.getBean("bookService", BookService.class);

        bookService.addBook(new Book("1", "《算法导论》", "存在"));
        // 更新成功,最终添加了: 1 记录

    }

}

然后我们看表,确实成功添加了。

(3) 修改删除功能

其实修改和删除功能,跟上面大同小异,无非只是修改了一下 String sql = xxx;的这一条代码。

在原有代码的基础上,我们修改对应的sql语句。

package com.w.spring5.dao;


import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {

        String sql = "delete from t_book where user_id = ?";
        //        Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};

        int update = jdbcTemplate.update(sql, book.getBookId());

        //        System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
        //        + update + " 条数据。");

        System.out.prizhentln(update);

    }

}

所以我们并不需要多余的设定,仅仅需要记住,Update这个方法,可以做到增加,删除和修改即可。

用法完全相同,不同的只有 sql语句的编写。

接下来,我们们去实现以下这几个功能,我们把他们整合到一起。

/*BookDao  -> interface*/
package com.w.spring5.dao;

import com.w.spring5.entity.Book;

public interface BookDao {

    // 添加功能
    void add(Book book);

    // 修改功能
    void update(Book book);

    // 删除功能 delete 根据id删除对应的整行(数据库主键为id)
    void delete(int id);

}
/* BookDaoImpl -> class */

package com.w.spring5.dao;


import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {

        String sql = "delete from t_book where user_id = ?";
        //        Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};

        int update = jdbcTemplate.update(sql, book.getBookId());

        //        System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
        //        + update + " 条数据。");

        System.out.println(update);

    }

    // 更新数据
    @Override
    public void update(Book book) {
        /*
        * 我们更新数据,分别更新了名称和状态,通过id进行更改。id为唯一主键,所以不用担心重复问题
        * */
        String sql = "update t_book set username=?, ustatus=? where user_id=?";

        // 这里的数组顺序要对应上面的占位符顺序,否则要通过下标取。
        Object[] o = {book.getBookName(), book.getBookActive(), book.getBookId()};

        int update = jdbcTemplate.update(sql, o);

        System.out.println("执行更新数据操作成功,共执行: " + update + " 条, 更新的内容为: {"
                           + book.getBookName() + ", 更新状态为: " + book.getBookActive() + "}");

    }

    // 删除数据
    @Override
    public void delete(int id) {

        String sql = "delete from t_book where user_id = ?";

        // 这里直接传入的就是一个数字,id,通过id删除对应的数据
        int update = jdbcTemplate.update(sql, id);

        System.out.println("删除数据成功, 共删除一条数据,数据的id为: " + id);

    }
}
/* BookService */
package com.w.spring5.service;

import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;


@Service("bookService")
public class BookService {

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

    // 添加
    public void addBook(Book book) {
        bookDao.add(book);
    }

    // 更新
    public void updateBook(Book book) {
        bookDao.update(book);
    }

    // 删除
    public void deleteBook(int id) {
        bookDao.delete(id);
    }

}

我们书写测试类来进行测试。一定要认真看我在代码里的注释。

import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void test() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        BookService bookService = context.getBean("bookService", BookService.class);

        // 我们更新 id 为 2 的数据,把Java编程实现,更新为Java编程思想。状态从缺货,更为存在。
        bookService.updateBook(new Book("2", "Java编程思想", "存在"));
        // 执行更新数据操作成功,共执行: 1 条, 更新的内容为: {Java编程思想, 更新状态为: 存在}

        // 我们删除id为三的数据
        bookService.deleteBook(3);
        // 删除数据成功, 共删除一条数据,数据的id为: 3

    }

}

现在,我们来查看库中存在的信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DV4ZudDa-1624522860166)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624517881514.png)]

更新成功,删除成功。

(4) 对数据库的查询功能

1. 查询返回单个值

什么是返回某一个值。举个简单的例子吧。我们查询的时候,我想知道,我的表里一共有多少条数据,怎么办。很简单的一个问题,我们都学过mysql, 我们使用聚合函数,COUNT(), 里面放上字段,或者 *。

呢么对应的sql语句为: select count(*) from [表名];

他返回给你的语句结果,是一个数值,一共有多少条记录,呢么我们要的就是这个数值。这也就是说的查询返回单个的值。

我们需要用到JdbcTemplate 的一个方法。

jdbcTemplate.queryForObject(String sql, Class requiredType)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEov2P4r-1624522860168)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624518792383.png)]

有两个参数,第一个参数,是我们的Sql语句,第二个参数,是一个Class类型,是我们的返回类型的Class,如果返回int类型的数据,呢么就是IntClass,如果是String类型的,则是StringClass。

举个例子,如果我们要一个int类型的返回值

jdbcTemplate.queryForObject(sql, Integer.class);

String呢?

jdbcTemplate.queryForObject(sql, String.class);

现在我们来去写一下案例,实现一个功能。我们查询一下t_book表中有多少条记录。

/* 首先我们先进行接口的编写, 既然要返回一个int数据类型,呢我们就给接口对应上对应的数据类型 */
// 查询表中共有多少条记录
Integer tableCount();

我们继续去实现对应的实现类,也就是BookDaoImpl

/* BookDaoImpl */
// 返回单个数据
@Override
public Integer tableCount() {

    String sql = "select count(*) from t_book";

    return jdbcTemplate.queryForObject(sql, Integer.class);

}

然后实现我们的逻辑层,ServiceBook类

/* ServiceBook */
// 查询单个数据。也就是实现我们的查询表内有多少条数据
public String selectCount() {

    Integer i = bookDao.tableCount();

    return "这张表里一共有: " + i + " 条数据!";
}
/* 去测试类里跑通我们的代码 */
// 查询单个数据,表里共有多少条数据
String s = bookService.selectCount();
System.out.println(s);
// 这张表里一共有: 2 条数据!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n6a2GyCR-1624522860170)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624519497396.png)]

可以看到,确实如此。

2. 查询返回对象

我们在什么情况下会用到这种情况呢,我们在商城买东西的时候,会展示图片和名称,在我们进行点击的时候,我们可以点击到对应的页面,看到物品的详细情况。根据id查询物品的详细信息,然后返回对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3i5Gpb5-1624522860171)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624519958538.png)]

这是我们需要用到的方法,具有三个参数。

第一个参数是我们的sql语句,

第二个参数 RowMapper,它本身是一个接口,返回不同类型的数据,使用这个接口里面的实现类,可以完成数据的封装。

第三个参数是我们传递sql语句中的呢个问号占位符的值。

到这里就要注意了,我们必须保证Book类的字段属性要和数据库字段名称完全一致。这里我们重写Book类

/*BOOK*/
package com.w.spring5.entity;

public class Book {

    private int user_id;

    private String username;

    private String ustatus;

    public Book() {}

    public Book(int user_id, String username, String ustatus) {
        this.user_id = user_id;
        this.username = username;
        this.ustatus = ustatus;
    }

    public int getUser_id() {
        return user_id;
    }

    public void setUser_id(int user_id) {
        this.user_id = user_id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUstatus() {
        return ustatus;
    }

    public void setUstatus(String ustatus) {
        this.ustatus = ustatus;
    }

    @Override
    public String toString() {
        return "Book{" +
            "user_id=" + user_id +
            ", username='" + username + '\'' +
            ", ustatus='" + ustatus + '\'' +
            '}';
    }
}

因为我们要返回的是一个Book对象,所以我们的接口方法也要定义返回类型为 Book。

// 查询返回对象。 根据id返回对应的book对象。
Book selectReturnObj(int id);

接下来去写对应的实现类方法

@Override
public Book selectReturnObj(int id) {

    String sql = "select * from t_book where user_id = ?";

    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);

}

这里三个参数都代表什么我有讲过,第二个为Spring帮你封装好的RowMapper对象。BeanPropertyRowMapper继承了RowMapper,里面的泛型里写什么就给你返回什么类型的对象。当然通过后面的括号里面给一个类的字节码也是可以的,如我上面的写法, Book.class, 第三个参数为 你要查询的 id。是几号。也就是sql的问号占位符占掉的地方。

3. 查询返回集合

我们在什么地方用的到他呢,我们查询图书列表的分页。也就是分页查询。用得到这个方法。

返回集合写法跟我们上面的返回对象类型写法差不多。我们在下面写一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dd5twe1-1624522860173)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624521473605.png)]

这是我们需要用到的方法,具有三个参数。

第一个参数是我们的sql语句,

第二个参数 RowMapper,它本身是一个接口,返回不同类型的数据,使用这个接口里面的实现类,可以完成数据的封装。

第三个参数是我们传递sql语句中的呢个问号占位符的值。

会发现他跟我们的返回对象类型一模一样的参数。

因为我们是查询全部,所以不需要写参数。

还是老样子,我们先处理BookDao接口的编写

 // 查询返回集合。
List<Book > selectReturnList();

然后 去实现对应的 Impl实现类

// 返回集合
@Override
public List<Book> selectReturnList() {

    String sql = "select * from t_book";

    List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));

    return query;
}

可以看到,query方法,默认就是返回List集合。

然后我们去编写逻辑层的Service代码。我们在Service代码中,做了一层循环处理。

// 返回集合对象。
public void selectReturnListBook() {

    List<Book> books = bookDao.selectReturnList();

    System.out.println("此处为元数据: " + books + " ..");

    System.out.println("");

    System.out.println("一下数据是经过处理的数据: ");

    ArrayList b = (ArrayList) books;

    for (Object o : b) {
        System.out.println(o);
    }

}

现在来到测试类里得到的最终结果。

// 这里为调用方法
bookService.selectReturnListBook();

/*
        此处为元数据: [Book{user_id=1, username='《算法导论》', ustatus='存在'}, Book{user_id=2, username='Java编程思想', ustatus='存在'}] ..

        一下数据是经过处理的数据:
        Book{user_id=1, username='《算法导论》', ustatus='存在'}
        Book{user_id=2, username='Java编程思想', ustatus='存在'}
        * */

(5) 小总结

如果我们需要对数据库进行增加,删除,修改的操作,我们需要调用 jdbcTemplate.update();方法,update里存在了两个参数,第一个是我们对应的SQL语句,第二个是不定长参数,用来替换掉Sql占位的地方。也就是传入值。

如果我们需要进行查询操作。我们就需要使用 jdbcTemplate.queryForObject, 在返回单一数据的时候,我们只需要传递两个参数即可,第一个是我们对应的sql语句,第二个参数为 我们返回的类型,String就写String.class,int就写Integer.class,等等。

如果我们需要查询返回对象结果,我们需要使用到三个参数,方法依然是jdbcTemplate.queryForObject, 第一个参数依然是我们的Sql语句,第二个参数是我们需要new一个对象。这个对象帮我们实现了返回值类型,new BeanPropertyRowMapper<>(Book.class),你给他泛型什么类型,他就会给你返回什么类型。第三个参数是你对应的占位符的值。

如果我们需要分页查询的场景,要返回集合类型的对象,只需要调用jdbcTemplate.query()方法即可。使用的方式和参数类型与上面不变,第三个参数不是必须要传递,上面也雷同。如果我们查询的是 select * from xxx; 这种不需要参数的,呢么我们写两个参数即可,sql语句和 new BeanPropertyRowMapper<>(Book.class), 他默认返回的就是一个List集合。

(6) 批量增删改功能

什么叫批量增删改,就是我可以同时向表中添加多条数据,修改多条数据,删除多条数据。

(1)首先是先批量添加的功能

在jdbcTemplate模板中, 有一个方法,这个方法就可以实现批量功能,

batchUpdate(String sql, List<Object[]> batchArgs);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2mCOGuz-1624526470471)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624523204293.png)]

第一个参数: sql语句。

第二个参数,List集合。表示你添加的多条记录的数据。List集合中的泛型是Object[] 数组。

下面我们使用代码来实现一下

接口的编写

// 批量添加
void batchAddBook(List<Object[] > o);

实现类的编写

@Override
public void batchAddBook(List<Object[]> o) {

    String sql = "insert into t_book values(?, ?, ?)";

    int[] ints = jdbcTemplate.batchUpdate(sql, o);

    System.out.println("共影响了: " + Arrays.toString(ints) + " 行");

}

Service的编写

// 添加多行数据
public void batchAddBook(List<Object[] > objects) {

    bookDao.batchAddBook(objects);

}

测试代码结果

// 添加多行数据
List<Object[] > objects = new ArrayList<>();

Object[] objects1 = {3, "Java二十一天速成", "缺货"};
Object[] objects2 = {4, "C++从入门到精通", "缺货"};
Object[] objects3 = {5, "Spring实战", "爆卖"};

objects.add(objects1);
objects.add(objects2);
objects.add(objects3);

bookService.batchAddBook(objects);
// 共影响了: [1, 1, 1] 行

最终结果,数组为: [1, 1, 1] 是什么意思呢,就是没一次执行,都会让一条语句改变,我们数组里有三次,所以执行了三次,改变了三行语句。我们在最后看数据库的表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nfl6TYqg-1624526470476)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624524366451.png)]

确实进行了添加数据。说明我们的批量结果成功了。

(2) 批量修改的功能

其实剩下的修改和删除基本上是一样的,他们都跟添加差不多,调的方法都是相同的,唯一不同的是参数和sql语句。

我们依然来编写接口的方法 (PS: 写了千万遍,我快写吐辣~~~)

// 批量修改
void batchUpdateBook(List<Object[]> objects);

Impl 改变的只有sql和传入的参数

@Override
public void batchUpdateBook(List<Object[]> objects) {

    String sql = "update t_book set username=?, ustatus=? where user_id=?";

    int[] ints = jdbcTemplate.batchUpdate(sql, objects);

    System.out.println(Arrays.toString(ints));

}

Service

// 批量修改
public void batchUpdate(List<Object[] > objects) {

    bookDao.batchUpdateBook(objects);

}

Test

// 批量修改
List<Object[] > list = new ArrayList<>();

// 第一个位置对应 名称, 第二个位置对应 状态, 第三个对应id
Object[] objects1 = {"C++怎么可能精通", "有货", 4};
Object[] objects2 = {"SpringBoot项目实战", "有货", 5};
Object[] objects3 = {"Java怎么可能速成", "有货", 3};

list.add(objects1);
list.add(objects2);
list.add(objects3);

bookService.batchUpdate(list);
// [1, 1, 1]

抱歉,图片丢了。

上图为修改前

下图为修改后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6skUz7x-1624526470479)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624525248983.png)]

确实修改成功了。

(3)批量删除功能

我不废话了啊,直接写代码了。感觉只要认真看了前面的,这里的不是问题。

/* interface */
// 批量删除 根据id去删除
void batchDeleteBook(List<Object[] > objects);

Impl

// 批量删除
@Override
public void batchDeleteBook(List<Object[]> objects) {

    String  sql = "delete from t_book where user_id = ?";

    int[] ints = jdbcTemplate.batchUpdate(sql, objects);

    System.out.println(Arrays.toString(ints));

}

Service

// 批量删除
public void batchDeleteBook(List<Object[] > objects) {

    bookDao.batchDeleteBook(objects);

}

测试代码

 // 批量删除
        List<Object[] > list = new ArrayList<>();

        Object[] o1 = {3};
        Object[] o2 = {4};
        Object[] o3 = {5};

        list.add(o1);
        list.add(o2);
        list.add(o3);

        bookService.batchDeleteBook(list);
        // [1, 1, 1]

    }

删除前

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwQp2wt8-1624526470480)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624526134954.png)]

删除后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t5fMAzYO-1624526470481)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624526148967.png)]

完结。

(7) 完整的Service,interface,Impl代码

Interface —> BookDao

package com.w.spring5.dao;

import com.w.spring5.entity.Book;

import java.util.List;

public interface BookDao {

    // 添加功能
    void add(Book book);

    // 修改功能
    void update(Book book);

    // 删除功能 delete 根据id删除对应的整行(数据库主键为id)
    void delete(int id);

    // 查询表中共有多少条记录
    Integer tableCount();

    // 查询返回对象。 根据id返回对应的book对象。
    Book selectReturnObj(int id);

    // 查询返回集合。
    List<Book > selectReturnList();

    // 批量添加
    void batchAddBook(List<Object[] > o);

    // 批量修改
    void batchUpdateBook(List<Object[]> objects);

    // 批量删除 根据id去删除
    void batchDeleteBook(List<Object[] > objects);

}

Impl

package com.w.spring5.dao;


import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.Arrays;
import java.util.List;

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {

        String sql = "delete from t_book where user_id = ?";
        //        Object[] o = {book.getBookId(), book.getBookName(), book.getBookActive()};

        int update = jdbcTemplate.update(sql, book.getUser_id());

        //        System.out.println("共更新了一条数据,更新的内容为: " + book.toString() + ", 共更新了 "
        //        + update + " 条数据。");

        System.out.println(update);

    }

    // 更新数据
    @Override
    public void update(Book book) {
        /*
        * 我们更新数据,分别更新了名称和状态,通过id进行更改。id为唯一主键,所以不用担心重复问题
        * */
        String sql = "update t_book set username=?, ustatus=? where user_id=?";

        // 这里的数组顺序要对应上面的占位符顺序,否则要通过下标取。
        Object[] o = {book.getUsername(), book.getUstatus(), book.getUser_id()};

        int update = jdbcTemplate.update(sql, o);

        System.out.println("执行更新数据操作成功,共执行: " + update + " 条, 更新的内容为: {"
                           + book.getUsername() + ", 更新状态为: " + book.getUstatus() + "}");

    }

    // 删除数据
    @Override
    public void delete(int id) {

        String sql = "delete from t_book where user_id = ?";

        // 这里直接传入的就是一个数字,id,通过id删除对应的数据
        int update = jdbcTemplate.update(sql, id);

        System.out.println("删除数据成功, 共删除一条数据,数据的id为: " + id);

    }

    // 返回单个数据
    @Override
    public Integer tableCount() {

        String sql = "select count(*) from t_book";

        return jdbcTemplate.queryForObject(sql, Integer.class);

    }

    // 查询返回对象


    @Override
    public Book selectReturnObj(int id) {

        String sql = "select * from t_book where user_id = ?";

        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);

    }

    // 返回集合
    @Override
    public List<Book> selectReturnList() {

        String sql = "select * from t_book";

        List<Book> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));

        return query;
    }

    // 批量添加
    @Override
    public void batchAddBook(List<Object[]> o) {

        String sql = "insert into t_book values(?, ?, ?)";

        int[] ints = jdbcTemplate.batchUpdate(sql, o);

        System.out.println("共影响了: " + Arrays.toString(ints) + " 行");

    }

    // 批量修改
    @Override
    public void batchUpdateBook(List<Object[]> objects) {

        String sql = "update t_book set username=?, ustatus=? where user_id=?";

        int[] ints = jdbcTemplate.batchUpdate(sql, objects);

        System.out.println(Arrays.toString(ints));

    }

    // 批量删除
    @Override
    public void batchDeleteBook(List<Object[]> objects) {

        String  sql = "delete from t_book where user_id = ?";

        int[] ints = jdbcTemplate.batchUpdate(sql, objects);

        System.out.println(Arrays.toString(ints));

    }
}

Service

package com.w.spring5.service;

import com.w.spring5.dao.BookDao;
import com.w.spring5.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;


@Service("bookService")
public class BookService {

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

    // 添加
    public void addBook(Book book) {
        bookDao.add(book);
    }

    // 更新
    public void updateBook(Book book) {
        bookDao.update(book);
    }

    // 删除
    public void deleteBook(int id) {
        bookDao.delete(id);
    }

    // 查询单个数据。也就是实现我们的查询表内有多少条数据
    public String selectCount() {

        Integer i = bookDao.tableCount();

        return "这张表里一共有: " + i + " 条数据!";
    }

    // 查询返回对象。
    public Book selectReturnObject(int id) {
        return bookDao.selectReturnObj(id);
    }

    // 返回集合对象。
    public void selectReturnListBook() {

        List<Book> books = bookDao.selectReturnList();

        System.out.println("此处为元数据: " + books + " ..");

        System.out.println("");

        System.out.println("一下数据是经过处理的数据: ");

        ArrayList b = (ArrayList) books;

        for (Object o : b) {
            System.out.println(o);
        }

    }

    // 添加多行数据
    public void batchAddBook(List<Object[] > objects) {

        bookDao.batchAddBook(objects);

    }

    // 批量修改
    public void batchUpdate(List<Object[] > objects) {

        bookDao.batchUpdateBook(objects);

    }

    // 批量删除
    public void batchDeleteBook(List<Object[] > objects) {

        bookDao.batchDeleteBook(objects);

    }


}

测试类

import com.w.spring5.entity.Book;
import com.w.spring5.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.List;

public class MyTest {

    @Test
    public void test() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        BookService bookService = context.getBean("bookService", BookService.class);

        // 我们更新 id 为 2 的数据,把Java编程实现,更新为Java编程思想。状态从缺货,更为存在。
        bookService.updateBook(new Book(2, "Java编程思想", "存在"));
        // 执行更新数据操作成功,共执行: 1 条, 更新的内容为: {Java编程思想, 更新状态为: 存在}

        // 我们删除id为三的数据
        bookService.deleteBook(3);
        // 删除数据成功, 共删除一条数据,数据的id为: 3

        // 查询单个数据,表里共有多少条数据
        String s = bookService.selectCount();
        System.out.println(s);

        System.out.println(bookService.selectReturnObject(1));
        // Book{user_id=1, username='《算法导论》', ustatus='存在'}
        System.out.println(bookService.selectReturnObject(2));
        // Book{user_id=2, username='Java编程思想', ustatus='存在'}

        bookService.selectReturnListBook();

        /*
        此处为元数据: [Book{user_id=1, username='《算法导论》', ustatus='存在'}, Book{user_id=2, username='Java编程思想', ustatus='存在'}] ..

        一下数据是经过处理的数据:
        Book{user_id=1, username='《算法导论》', ustatus='存在'}
        Book{user_id=2, username='Java编程思想', ustatus='存在'}
        * */

        // 添加多行数据
        List<Object[] > objects = new ArrayList<>();

        Object[] objects1 = {3, "Java二十一天速成", "缺货"};
        Object[] objects2 = {4, "C++从入门到精通", "缺货"};
        Object[] objects3 = {5, "Spring实战", "爆卖"};

        objects.add(objects1);
        objects.add(objects2);
        objects.add(objects3);

        bookService.batchAddBook(objects);
        // 共影响了: [1, 1, 1] 行


        // 批量修改
        List<Object[] > list1 = new ArrayList<>();

        // 第一个位置对应 名称, 第二个位置对应 状态, 第三个对应id
        Object[] objects11 = {"C++怎么可能精通", "有货", 4};
        Object[] objects22 = {"SpringBoot项目实战", "有货", 5};
        Object[] objects33 = {"Java怎么可能速成", "有货", 3};

        list1.add(objects11);
        list1.add(objects22);
        list1.add(objects33);

        bookService.batchUpdate(list1);
        // [1, 1, 1]


        // 批量删除
        List<Object[] > list = new ArrayList<>();

        Object[] o1 = {3};
        Object[] o2 = {4};
        Object[] o3 = {5};

        list.add(o1);
        list.add(o2);
        list.add(o3);

        bookService.batchDeleteBook(list);
        // [1, 1, 1]

    }

}


7. 事务操作

什么是事务

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败,呢么所有的操作都会失败。

典型场景:

银行转账的场景。比如现在有两个人,一个叫张三,一个叫李四。现在的过程中,李四,想要转账100 元给 张三。

逻辑是什么呢, 李四,少100,张三,多100。在李四少完100后,张三才能多100。假设这个转账过程中,出现了异常,比如最典型的网络断开,李四,少了100,但是张三不会多。这种情况,就会引起异常。最终的结果就是,李四没有少,张三没有多。

所以就是,要么都成功,要么全失败。

事务四大特性(ACID)

(1)原子性

他指的就是,逻辑上一组操作,要么都成功,如果有一个失败,呢么所有的操作都会失败。

(2)一致性

操作之前和操作之后他的总量是不变的。举例子,张三和李四一人有100元,加在一起就是200元,然后张三转给李四100元,张三为0,李四为200,他们加在一起还是200,总数不变。

(3)隔离性

在多事务之间进行操作的时候,他们不会产生影响。

(4)持久性

在最后的数据,我们是要提交的,提交之后,表内的数据,真正的发生了变化。

搭建事务操作环境

我们来搭建一个银行转账的情况,来实现事务的环境。

已经学习到了Spring框架,呢么肯定学习了JavaEE的部分,JavaEE的三层架构,Web层,Service层,和Dao层。

Web层也可以叫做视图层,用来做前端部分,Service也可以叫做业务逻辑层。Dao也叫做数据持久层,我们的数据库一些操作就在这里完成。

现在我们按照这个结构,把环境做一个搭建。我们主要在Service层和Dao层。

转账的操作,一个少钱,一个多钱。

所以我们在Dao层,创建两个方法,一个少钱,一个多钱。

然后我们来到业务逻辑层,创建一个转账的方法。我们可以在转账的方法内,调用Dao的两个方法。

我们的第一步应该是什么?因为我们要操作数据库,所以应该是新建一个数据库。然后新建一个表。然后添加记录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxUQNMF6-1624779257995)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624540333307.png)]

表的设计。数据库名称为: user_db, 表名称为: t_account,在表里插入两条数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-grB3TxyA-1624779257999)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624540519524.png)]

等等我们需要实现,张三,给李四转账 100 元。

接下来需要我们做的是,配置连接池,开启注解扫描支持,创建Dao,Service,并且注入对应的属性关系。完成对象的创建。

Service 注入 Dao。 Dao注入JdbcTemplate,在JdbcTemplate中注入DataSource

配置XML文件,开启注解扫描,配置连接池,创建JdbcTemplate对象。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation=
               "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 开启注解组件扫描-->
    <context:component-scan base-package="com.w.spring5" />

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

完成对象的创建以及属性的注入。

UserService

package com.w.spring5.service;


import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserService {

    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;

}

UserDao

package com.w.spring5.dao;

public interface UserDao {
}

UserDaoImpl

package com.w.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {

    @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate;


}

接下来写,具体的实现部分。在Dao里面有两个方法,一个少钱,一个多钱。在Service里调用这两个方法。实现对应的转账逻辑

我们要实现张三转100给李四。

Dao 和 Impl部分

Dao

package com.w.spring5.dao;

public interface UserDao {

    // 增加钱的方法。
    void addMoney();

    // 减少钱的方法
    void reduceMoney();


}

Impl

package com.w.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {

    @Autowired
    @Qualifier("jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    // 我们要实现张三转100给李四。

    // 少钱
    @Override
    public void reduceMoney() {
        String sql = "update t_account set money = money-? where username = ?";
        // 上面sql语句的意思是。更新 t_account表的数据, 设置 money 的值为 money 减去 参数1
        // 设置的方式为 username 等于 张三的时候,执行前面的内容。
        jdbcTemplate.update(sql, 100, "张三");
    }

    // 多钱
    @Override
    public void addMoney() {
        String sql = "update t_account set money = money+? where username = ?";
        // 只是改了个人名而已,具体看上面实现。
        jdbcTemplate.update(sql, 100, "李四");
    }

}

Service

package com.w.spring5.service;


import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserService {

    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;

    // 转账的方法
    public void accountMoney() {
        // 张三少一百
        userDao.reduceMoney();

        // 李四多100
        userDao.addMoney();

    }

}

这样,我们的环境搭建就已经完成了。最终我们做一个测试

import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void test() {

        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        UserService userService = context.getBean("userService", UserService.class);

        userService.accountMoney();

    }

}

在我们执行之后,他并不会给我们任何反馈(反正没报错)。我们这个时候去看表的信息,就会发现,张三的钱,变成了900,而李四的钱,变成了1100。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YlRzvFEM-1624779258002)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624542517551.png)]

注意,这仅仅是环境搭建好了而已。

按照我们上面的转账代码,如果他在完全正常的情况下,他肯定是没有问题的。但是如果运行的过程中,代码产生了异常,就会出现问题、

我们在service中模拟一个异常

import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserService {

    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;

    // 转账的方法
    public void accountMoney() {
        // 张三少一百
        userDao.reduceMoney();

        // 模拟异常
        int a = 10 / 0;

        // 李四多100
        userDao.addMoney();

    }

}

在这里报异常之后,我们会执行张三少一百,但是李四多100,并不会增加。

我们现在数据库中,把双方的钱还原,张三-1000, 李四-1000

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eeHnscDb-1624779258005)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624597724119.png)]

然后我们运行程序。得到以下结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDYGQtDe-1624779258007)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1624599806851.png)]

我们发现,张三的钱,扣掉了100元,但是李四并没有多出对应的钱,我们的钱从总数2000,变为了1900。按照真实的场景,在张三少完钱后,李四的钱没有多,这样会触发异常,导致回滚。张三的钱也不应该少。

呢么如何解决上述的问题呢,我们就需要使用事务来进行操作,要么全部成功,要么全部失败。

添加事务到JavaEE三层结构里面 Service 层 (业务逻辑层)

在Spring进行事务管理操作,有两种方式,第一种叫做编程时事务管理。第二种叫做声明式事务管理。(在实际开发中,我们用的较多的是声明式事务管理。)

声明事务管理

我们会选择使用两种方式, 第一种是基于XML做到的。第二种则是基于注解方式实现的。注解方式是我们重点是用。因为简洁和方便。

基于注解实现

首先我们在Spring的配置文件中。配置事务管理器。 说是配置事务管理器,其实跟JdbcTemplate一样,只是把他的主接口给创建出来,然后塞到Bean容器中 并且在内容里注入数据源。

<!-- 创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 指定数据源-->
    <property name="dataSource" ref="dataSource" />
</bean>

在Spring配置文件,开启事务注解。

在Spring的配置文件中,我们需要先引入一个命名空间,tx。用于做跟事务相关的东西。然后开启事务注解

<tx:annotation-driven transaction-manager=“dataSourceTransactionManager” />

transaction-manager表示,你要开启哪一个事务注解的部分,这里我们把前面配置的事务管理器给他传入。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation=
       "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 开启注解组件扫描-->
    <context:component-scan base-package="com.w.spring5" />
    <!-- 开启事务的注解 -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="username" value="root" />
        <property name= "password" value="root" />
    </bean>

    <!-- 创建事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 指定数据源-->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

然后我们需要在Service的类上面,或者在类方法里面,添加一个事务的注解。如果把它标注到类上面,就表示,这个类里的所有的方法,都添加上了事务。如果把这个注解添加到类里面的方法上面,他只会为你这个方法添加事务。

@Transactional

package com.w.spring5.service;


import com.w.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("userService")
@Transactional  // 这个注解可以加到类上,也可以加到方法上去。
public class UserService {

    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;

    // 转账的方法
    public void accountMoney() {

        // 第一步 开启事务

        // 第二步,进行业务操作
        userDao.reduceMoney();

        // 模拟异常
        int a = 10 / 0;

        // 李四多100
        userDao.addMoney();

        // 第三步,没有发生异常,提交事务。
    }

}

在加上这个注解之后,我们在去测试类中重新跑一下代码,当然,在跑i前别忘了把张三的数据还原为1000;

他依然会给我们报错,和上一次的一样,但是这次打开表去观察数据,发现数据并没有发生改变,而是依然是1000 + 1000的结果。这就是事务。

事务参数(传播行为)

在我们的注解@Transactional后面,在这个注解里可以配置针对事务的一些相关的参数。
在这里插入图片描述

现在我们用到的几个参数有

propagation: 事务的传播行为

什么是事务传播行为,假设我们现在有两个操作数据库的方法,一个叫add,一个叫update。 再假设。add上被标注了事务,而update却没有被标注事务。呢么我们要在add里调用update,应该怎么做。也有可能是两个方法都有事务,各种情况。

在Spring框架中,事务的传播行为共有七种。

REQUIRED: 我们假设在这里,add方法本身有自己所属的事务,在add方法里调用update方法。呢么 update方法,会使用当前 add方法里的事务。但是如果add方法本身没有事务,调用update方法之后,创建新的事务进行操作。

比较官方一点的说法(死板): 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,在自己的事务内运行。

REQUIRED_NEW: 讲的通俗一点,我们还是假设add方法存在事务,update不存。我们在add内调用update方法,她不管你是否在add方法上存在事务,存在也好,不存在也好。他都会去给你新建事务。也就是说,add方法里,有事务,但我就是不用。我去给你新建,没有我也给你新建。

比较官方一点的说法(死板):当前的方法必须启动新的事务,并在他自己的事务内运行,如果有事务正在运行,应该将他挂起。

在Spring中有七种行为,这两种最常见剩余五种我一一列举,要记住前两个

SUPPORTS: 如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中。

NOT_SUPPORTED : 当前的方法不应该运行在事务中,如果有运行的事务,就将他挂起。

**MANDATORY:**当前的方法必须运行在事务内部,如果没有正在运行的事务,则抛出异常。

NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。

**NESTED:**如果有事务在运行。当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在他自己的事务内运行。

如何去使用这个传播行为,我们在注解的属性上加上 @Transactional(propagation = Propagation.REQUIRED)

也就是加上一个 propagation = Propagation.行为

我们如果不写的话,它默认就是上面呢个REQUIRED。

ioslation: 事务的隔离级别
(1) 事务有个特性,称为隔离性 。多事务操作之间,他们不会产生影响。

不考虑隔离性,会产生一系列的问题。总结起来有这么几个读的问题。

脏读,不可重复读,虚读/幻读。

脏读: 一个未提交的事务,读取到了另一个未提交的事务的数据。这是一个致命问题。

不可重复读:一个未提交的事务,读取到了另一个提交事务中的修改的数据。这是一种现象。

虚度:一个未提交的事务,读取到了另一个提交事务添加数据。

但是问题已经出现了,要怎么解决呢,就用到了事务的隔离性。就可以解决这几个问题。通过设置事务的隔离性,就能解决这三个问题。

事务的隔离级别

脏读不可重复读幻读
READ UNCOMMITTED (读未提交)
READ COMMITTED (读已提交)
REPEATABLE READ(可重复读)
SERIALIZABLE (串行化)

具体操作我们只需要在注解中添加属性, isolation = Isolation.SERIALIZABLE, lsolation后面可以跟上面的四个值。来解决对应的操作。

我们使用的Mysql默认的级别,就是可重复读。

timeout: 超时时间

这个很好理解,我们在创建事务之后,会有一个提交的操作,我们规定在多长时间内进行事务提交,如果这段时间内没有提交,呢么就进行事务回滚。总结下来就是,事务需要在一定的时间内进行提交,如果不提交,呢么就要做事务的回滚。

在注解中,属性的默认值为: -1 ,也就是无超市。我们可以进行设置,设置是以秒为单位,进行计算。

timeout = 20 表示二十秒内,如果没有进行事务提交,就进行事务回滚。

readOnly: 是否只读

读: 查询操作。写: 添加,修改,删除操作。

reanOnly默认值是 false。 表示你可以查询,也可以进行增加,修改,删除。如果改为true,呢么就只能进行查询操作。

readOnly = false; 默认值。

rollbackFor: 回滚

设置出现哪一些异常进行事务回滚。

noRollbackFor: 不回滚

设置出现哪一些异常不进行回滚。

完全注解开发

我们在先前配置AOP的时候,完全注解开发,是要先配置一个配置类,然后在配置类内中进行配置。让他代替XML文件。并且先对德鲁伊连接池进行配置。

package com.w.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration // 代表这是一个配置类
@ComponentScan(basePackages = {"com.w.spring5"})    // 开启注解扫描
@EnableTransactionManagement // 开启我们的事务
public class TxConfig {

    // 创建数据库的连接池
    @Bean
    public DruidDataSource getDruidDataSource() {

        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 驱动名称
        druidDataSource.setUrl("jdbc:mysql:///user_db");   // 数据库地址
        druidDataSource.setUsername("root");  // 数据库用户名
        druidDataSource.setPassword("root"); // 数据库密码

        return druidDataSource;
    }

}

通过注解创建Bean容器,其实也是很简单的,只需要在方法上面添加@Bean。然后在方法里面return回去一个对应的对象。即可。然后通过set方法,进行设置值。 具体的方法对应具体的值在代码中的注释有解释。

// 我们还需要创建JdbcTemplate模板对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
    // 到IOC容器中,根据类型找到DataSource。
    return new JdbcTemplate(dataSource);
}

我们要使用IOC容器中的对象,在方法内进行一个参数的书写,这个参数会在IOC容器中根据类型自动注入。因为DataSource是Java提供的API,阿里巴巴提供的DruidDataSource实现了DataSource,也就是他是这个接口的子接口,我们可以直接根据类型注入进去。

我们开启事务的支持。

// 创建事务管理器
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {

    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();

    dataSourceTransactionManager.setDataSource(dataSource);

    return dataSourceTransactionManager;
}

原理跟JdbcTemplate差不多。好好读一下代码。

然后我们进行测试类的书写,我在这里出了个错误,错误是创建UserDaoImpl为Bean容器时失败,原因是jdbcTemplate注入属性时失败。原因是我在上面进行了指定名称注入,所以。大家要把他对应的代码注解给删掉。然后还要删除掉UserService里的手动异常,整数除以0.这样代码就可以跑起来。张三少100,李四多100;

import com.w.spring5.config.TxConfig;
import com.w.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {

    @Test
    public void test() {

        ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);

        UserService userService = context.getBean("userService", UserService.class);

        userService.accountMoney();

    }

}

执行成功,张三少100,李四多100。

完整的配置类代码。

package com.w.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration // 代表这是一个配置类
@ComponentScan(basePackages = {"com.w.spring5"})    // 开启注解扫描
@EnableTransactionManagement // 开启我们的事务
public class TxConfig {

    // 创建数据库的连接池
    @Bean
    public DruidDataSource getDruidDataSource() {

        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 驱动名称
        druidDataSource.setUrl("jdbc:mysql:///user_db");   // 数据库地址
        druidDataSource.setUsername("root");  // 数据库用户名
        druidDataSource.setPassword("root"); // 数据库密码

        return druidDataSource;
    }

    // 我们还需要创建JdbcTemplate模板对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        // 到IOC容器中,根据类型找到DataSource。
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    // 创建事务管理器
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {

        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();

        dataSourceTransactionManager.setDataSource(dataSource);

        return dataSourceTransactionManager;
    }

}

  • 28
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
Spring是一个轻量级的、非侵入式的IOC和AOP的一站式的Java开发框架。它于2003年兴起,旨在简化企业级应用开发。Spring的核心功能的jar包大小较小,不需要业务代码继承或实现Spring中的任何类或接口。Spring通过控制反转(IOC)将对象的创建权交给框架,而面向切面编程(AOP)是一种编程思想,是面向对象编程(OOP)的一种补充。Spring还提供了数据访问功能和Web功能,并可以很好地管理其他框架。\[2\] 在Spring中,可以使用注解来描述Bean,其中@Repository是一个泛化的概念,表示一个组件(Bean),可以应用在任何层次。只需将该注解标注在相应的类上即可。\[3\] 总之,Spring是一个功能强大的框架,提供了IOC和AOP的支持,可以简化企业级应用开发,并且具有灵活的注解机制来描述Bean。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Spring详解(超全面)](https://blog.csdn.net/ailaohuyou211/article/details/130394148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [spring全面详解](https://blog.csdn.net/weixin_71243923/article/details/128267166)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

王子良.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值