Spring基础

Spring

1.分析传统开发存在的问题

​ 每个新的技术出现都是为了解决传统的一些弊端,所以在分析Spring技术之前我们要来观察传统的开发存在哪些问题,你才能知道spring解决的是什么问题。传统开发存在的最大问题是代码的耦合度高,那么我们观察传统开发。

//1.创建一个Emp的dao层接实现类1

package Dao02.impl;
import Dao02.IEmpDao02;

public class MysqlEmpDaoImpl implements IEmpDao02 {
    @Override
    public void inset() {
        System.out.println("操作的是Mysql数据库!");
    }
}

//2.创建一个Emp的dao层接实现类2
package Dao02.impl;
import Dao02.IEmpDao02;
public class OracleEmpDaoImpl implements IEmpDao02 {
    @Override
    public void inset() {
        System.out.println("操作的是Oracle数据库!");
    }
}


//3.实现类
public class TestFactory {
    public static void main(String[] args) throws Exception {
        /*取得数据层实现类对象*/
        IEmpDao02 empDao02 = new OracleEmpDaoImp();
        empDao02.inset();
        /**
         * 码该结构改变之后那么业务层的代码也随之要改变,
         * 这样的代码造成了业务层对数据层的依赖过高,也叫做耦合度高,
         * 这样的依赖是关键字new导致的,为了解决这样的问题出现了一种思想:
         * 将对象的实例化过程封装到一个工厂中,
         * 这种思想叫做工厂设计模式(这里要用到反射)
         * */
    }
}

工厂类的设置

package Dao02.factory;
public class Factory {

                                /** 传入 类的全路径 */
    public static Object getInstance(String className){
        try {
            return Class.forName(className).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        return null;
        }
    }
                            /** 传入 类名.class */
    public static Object getInstance(Class cla){
        try {
            return cla.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
return Class.forName(className).newInstance();
    }
}


@Test
public void test01(){
    /* 使用工厂类 */
        /*方式1---传 类.class 文件*/
        IEmpDao02 instance =(IEmpDao02) Factory.getInstance(MysqlEmpDaoImpl.class);
        instance.inset();
        /*方式二---反射传全路径*/
        IEmpDao02 instance1 =(IEmpDao02) Factory.getInstance("Dao02.impl.OracleEmpDaoImpl");
        instance1.inset();
}

1.1 将耦合转移到配置文件中

  • 导入dom4j的开发包:是用来解析xml文件中的节点的数据或者属性的值的一个工具在-pom.xml-maven的配置文件中配置
 <!--dom4j的开发包-->
<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6</version>
</dependency>
  • 准备配置文件

该配置文件保存到resources目录下,名字叫做spring.xml 放在resources文件下,在国外会将对象叫做bean,bean翻译成中文就是豆子。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean  class="cn.mbzvip.dao.impl.MysqlEmpDAOImpl"></bean>
</beans>

​ 使用了工厂模式在一定程度上降低了代码的耦合(解耦), 但是还是不完美,因为还需要做小的改动。
如果此时要求程序中不想改变任何代码,也就是在Java代码中实现“零改动”,那么只能将这种耦合转移,从代码中转移到配置文件中,那么在以后只需要修改配置文件中的内容即可。

    /**
     * 工厂类的第三中方法:
     * 从 spring.xml配置文件中读取数据:
     * */
    public static Object getInstance()throws Exception{

        //读取配置文件中的信息,以流的方式读取过来
        InputStream inputStream = Factory.class.getResourceAsStream("/spring.xml");
        //创建解析配置文件的对象
        SAXReader saxReader = new SAXReader();
        /* 创建一个表示文件结构的对象*/
        Document document = saxReader.read(inputStream);
        //取得文档的根元素节点
        Element rootElement = document.getRootElement();
        //取得根节点下的子节点
        List<Element> elements = rootElement.elements();
        //取得集合中的第一个节点
        Element firstElement = elements.get(0);
        //通过第一个子节点取得类的全名称
        String className = firstElement.attributeValue("class");
        //使用反射实例化对象
    }


@Test
public void test01(){
    IEmpDao02 instance2 = (IEmpDao02) Factory.getInstance();
        instance2.inset();
}

​ 以上的代码描述的是一种思想:将耦合转移到配置文件中去实现(后面还可以使用注解) 。

​ 在传统的开发中是需要在类中直接实例化所需要的(依赖的)类对象,现在我们将对象的创建以及管理放到工厂中实现,而且工厂可以通过读取配置文件来管理对象之间的依赖关系。这种方式可以叫做反转控制(Inversion of control ) ioc,这种工厂可以叫做ioc容器,反转控制的说法是相对于传统的开发而出现了,将对象的创建管理交给了容器。

​ 其实Spring就是一个工厂+代理模式设计出来的一个ioc容器,Spring管理了对象的创建(生命周期等)以及对象与对象之间的协作关系,降低了程序之间的耦合度。在这里插入图片描述

2.Spring开发环境

2.1创建项目结构

​ web结构的项目:就是可以整合前端开发以及web服务器(比如Tomcat/webservice/jetty)的项目,也叫作动态项目。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2 导入Spring的基础开发包

目前是3组导入maven项目中的pom.xml中

<!--spring的核心组件的开发包,核心工具类都在该jar下面了-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>
<!--管理spring容器中的对象的依赖包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>
<!--为spring的所有程序运行提供了上下文环境-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>

以上的三个开发包的作用:

  • beans:这个包下的所有类主要解决了三件事:Bean 的定义、Bean 的创建以及对 Bean 的解析。对 Spring 的使用者来说唯一需要关心的就是Bean获取,Spring Bean 的创建是典型的工厂模式。
  • context:他实际上就是给 Spring 提供一个运行时的上下文环境,用以保存各个对象的状态,其实这就是对象存在的上下文环境(就比如servlet要运行 需要tomcat启动提供上下文环境一样)。
  • core:为beans以及context的运作提供了工具类。

2.3 容器中注册bean

需要在web.xml文件中配置Spring的监听器(暂时用不到监听器)

​ spring和mybatis一样也有自己的配置文件,这个配置文件的位置我们需要在web.xml文件中定义出来(暂时不用管)。

  • 在resourecs文件下创建spring的配置文件,名字叫做applicationContext.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-3.0.xsd
      ">
    <!-- 当spring程序解析到该配置的时候会使用 对应类创建对象保存到ioc容器中(叫做bean的注册)
          被保存到容器中的对象的名字就是id值
     -->
   <!-- 容器中注册bean-:需要有实体类的支撑-->
    <bean id="oracleBean" class="Dao02.impl.OracleEmpDaoImpl"></bean>
    <bean id="mysqlBean" class="Dao02.impl.MysqlEmpDaoImpl"></bean> 
</beans>

2.4 取得注册的Bean

public class Spring_bean {
    /**
     * 获取注册的Bean
     * */
    public static void main(String[] args) {
        //加载spring的配置文件并且会自动生成一个spring运行的上下文环境(容器)所有被注册过的bean都在该容器中
        ApplicationContext  applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中取得需要的bean(根据对象的名字获取叫做byName)
        IEmpDao02 oracleBean = (IEmpDao02) applicationContext.getBean("oracleBean");
        //根据类型获取(byType)
        IEmpDao02 mysqlBean = (IEmpDao02) applicationContext.getBean(MysqlEmpDaoImpl.class);
        //调用对象的方法
        oracleBean.inset();
        mysqlBean.inset();
    }
}

3. Spring的对象Bean的作用域

​ 所谓的bean的作用域就是描述一个对象在容器中的作用范围,Spring的bean的作用域有五种,稍后再看具体内容。

<?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-3.0.xsd ">
    <!-- 当spring程序解析到该配置的时候会使用 对应类创建对象保存到ioc容器中(叫做bean的注册)被保存到容器中的对象的名字就是id值-->
    
    <bean id="oracleBean1" class="cn.qf.dao.impl.OracleEmpDaoImpl"></bean>
    <bean id="oracleBean2" class="cn.qf.dao.impl.OracleEmpDaoImpl"></bean>
    <bean id="msqlBean" class="cn.qf.dao.impl.MysqlEmpDaoImpl"></bean>
</beans>

@Test
public static void main(String[] args) {
        //加载spring的配置文件  并且会自动生成一个spring运行的上下文环境(容器) 所以被注册过的bean都在该容器中
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中取得需要的bean(根据对象的名字获取 叫做  by Name)
        IEmpDAO  oracleBean1=(IEmpDAO)context.getBean("oracleBean1");
        IEmpDAO  oracleBean2=(IEmpDAO)context.getBean("oracleBean2");
        /**
        * 此方法会报错,根据类型来获取容器中有四个相同类型的bean,主方法中根据类型获取就无法确定要获取哪一个,所以报错。
        */
     	IEmpDAO  oracleBean3=(IEmpDAO)context.getBean(OracleEmpDaoImpl.class);
        System.out.println(oracleBean1);
        System.out.println(oracleBean2);
}


@Test
public static void main(String[] args) {
        //加载Spring的配置文件 并且生成一个Spring的上下文环境的对象 我们所有的bean都在该容器中
        ApplicationContext  ctx= new ClassPathXmlApplicationContext("applicationContext.xml");
        //取得容器中的对象(byName:根据名字获取)
        IEmpDAO  oracleDAO1=(IEmpDAO)ctx.getBean("oracleBean");
        IEmpDAO  oracleDAO2=(IEmpDAO)ctx.getBean("oracleBean");
        IEmpDAO  oracleDAO3=(IEmpDAO)ctx.getBean("oracleBean");
        //打印的两个对象的地址是否相同? 如果相同则表示他们同一个对象
        System.out.println(oracleDAO1);
        System.out.println(oracleDAO2);
        System.out.println(oracleDAO3); 
}

默认的是单例模式,可以进行其他配置,总共有五种作用域范围。

面试题:请说明Spring的bean的作用域有哪些?说明他们之间的区别?

在这里插入图片描述

​ request模式和我们之前讲解servlet的时候概念以及它的生命周期都是和request内置对象一样的。

<!-- 单例模式的对象 scope属性-->
<bean id="account" class="com.spring.master.Account" scope="prototype"/>  

<bean id="account" class="com.spring.master.Account" singleton="false"/>

<!-- 单例模式的对象 scope属性-->
<bean id="loginAction" class=cn.spring.master.LoginAction" scope="request"/>
<bean id="userPreferences" class="com.spring.master.UserPreferences" scope="session"/>                                                                         

4.依赖注入的概念

​ 在之前我们要为一个对象的属性赋值可以使用构造方法初始化,还可以在创建对象之后调用setter方法为对象的属性赋值。 在Spring中是使用依赖注入的方式为对象的属性赋值,如果要为依赖注入做一个定义:所谓的依赖注入是根据配置文件或者注解中给定信息为bean(对象)的属性指定引用关系或者赋值。

​ 依赖注入有三种方式:分别是构造器注入、setter方法注入、接口注入(历史悠久,但是现在不用了,作为了解)

  • 创建一个Emp类和Dept类
package cn.qf.pojo;
public class Emp implements Serializable,Cloneable{
    private Integer empno;
    private String ename;
    private String job;
    private Double sal;
    private Dept dept;
    private String[] ints; //兴趣爱好
    private List<String> friends; //朋友
    private Map<String,String> names; //绰号
    //构造器、getter\setter、toString省略
}

public class Dept implements Serializable,Cloneable{
    private Integer deptno;
    private String dname;
    private  String loc;
    private List<Emp> emlList; //自定义集合List
     //构造器、getter\setter、toString省略
}

4.1构造器注入

  • 必须要依赖于已经拥有的构造器!!!

构造器注入等价于调用了我们的有参构造为对象的属性初始化的效果。

<?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-3.0.xsd
      ">
    <!-- 当spring程序解析到该配置的时候会使用 对应类创建对象保存到ioc容器中(叫做bean的注册)
          被保存到容器中的对象的名字就是id值
     -->
<!-- 容器中注册bean-->
    <bean id="oracleBean" class="Dao02.impl.OracleEmpDaoImpl"></bean>
    <bean id="mysqlBean" class="Dao02.impl.MysqlEmpDaoImpl"></bean>


<!-- 构造器注入:必须要提供形参相同的构造器才可使用构造器注入 -->
    <bean id="emp" class="cn.qf.pojo.Emp" lazy-init="true">
       <!--使用构造器注入的方式为对象的属性设置初始值-->
        <constructor-arg index="0" value="1001" />
        <constructor-arg index="1" value="兵兵"/>
        <constructor-arg index="2" value="网关"/>
        <constructor-arg index="3" value="3000.0"/>
        <constructor-arg index="4" ref="dept"/> <!-- 属性为其它类型时 ref=进行配置 -->
<!-- 引用 -->
    </bean>
<!-- 延迟加载-懒加载  lazy-init=  -->
    <bean id="dept" class="cn.qf.pojo.Dept" lazy-init="true">
        <constructor-arg index="0" value="10"/>
        <constructor-arg index="1" value="研发部门"/>
        <constructor-arg index="2" value="贵阳"/>
    </bean>
</beans>

4.2 setter注入

​ setter注入,等价我们之前的先创建了对象 之后调用setter为对象属性初始化值。

<?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-3.0.xsd
      ">
    <!-- 当spring程序解析到该配置的时候会使用 对应类创建对象保存到ioc容器中(叫做bean的注册)
          被保存到容器中的对象的名字就是id值
     -->
<!-- 容器中注册bean-->
    <bean id="oracleBean" class="Dao02.impl.OracleEmpDaoImpl"></bean>
    <bean id="mysqlBean" class="Dao02.impl.MysqlEmpDaoImpl"></bean>


<!-- 构造器注入:必须要提供形参相同的构造器才可使用构造器注入 -->
    <bean id="emp" class="cn.qf.pojo.Emp" lazy-init="true">
       <!--使用构造器注入的方式为对象的属性设置初始值-->
        <constructor-arg index="0" value="1001" />
        <constructor-arg index="1" value="兵兵"/>
        <constructor-arg index="2" value="网关"/>
        <constructor-arg index="3" value="3000.0"/>
        <constructor-arg index="4" ref="dept"/> <!-- 属性为其它类型时 ref=进行配置 -->
                                     <!-- 引用 -->
    </bean>
                                         <!-- 延迟加载-懒加载 -->
    <bean id="dept" class="cn.qf.pojo.Dept" lazy-init="true">
        <constructor-arg index="0" value="10"/>
        <constructor-arg index="1" value="研发部门"/>
        <constructor-arg index="2" value="贵阳"/>
    </bean>

<!-- setter注入:-注入基本数据类型和自定义类型and List、Map——(需要提供setter方法) -->
    <bean id="emp02" class="cn.qf.pojo.Emp">
        <property name="ename" value="小刘"/>
        <property name="job" value="java开发工程师"/>
        <property name="sal" value="7000"/>
        <!--注入的是自定义的数据类型 ,ref引用其它bean的id-->
        <property name="dept" ref="dept02"/>
        <!--为数组类型的属性注入值-->
        <property name="ints">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
            </array>
        </property>
        <!--注入集合类型的数据-->
        <property name="friends">
            <list>
                <value>浩浩</value>
                <value>坤坤</value>
            </list>
        </property>
        <!--注入map集合类型的数据-->
        <property name="names">
            <map>
                <entry key="小时候" value="刘刘"></entry>
                <entry key="现在" value="江哥"/>
            </map>
        </property>
    </bean>

    <bean id="dept02" class="cn.qf.pojo.Dept">
        <property name="dname" value="技术部门"/>
        <property name="deptno" value="10"/>
        <!-- 自定义集合类型的注入 -->
        <property name="emlList">
            <list>
                <bean class="cn.qf.pojo.Emp">
                    <property name="ename" value="小李"></property>
                    <property name="job" value="C++工程师"></property>
                </bean>
                <bean class="cn.qf.pojo.Emp"></bean>
            </list>
        </property>
    </bean>

</beans>

4.3 自定义类型其它注入方式

在之前我们如果要注入自定义类型使用的是“ref”引用其他类型,那么我们还可以使用其他方式进行注入

bean对象的autowire属性的几种用法
1.autowire="no" -- (默认)不采用autowire机制。当我们需要使用依赖注入,只能用 ref
2.autowire="byName" --通过属性的名称自动装配(注入)
3.autowire="byType" -- 通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
4.autowire="construtor" -- 类似于byType,但是是通过构造函数的参数类型来匹配。
5.autowire="default" -- 采用父级标签的配置。(即beans的default-autowire属性)
<!--autobyte = "bytpe"
使用了自动注入则会从容器中查找一个和你的自定义的属性类型匹配的对象赋值
-->
 
    <bean id="emp" class="cn.qf.pojo.Emp" lazy-init="true" autowire="bytype">
        <!--使用构造器注入的方式为对象的属性设置初始值-->
        <property name="empno" value="1001" />
        <property name="ename" value="兵兵"/>
        <property name="job" value="网关"/>
        <property name="sal" value="3000.0"/>
       <!--引用 <property name="4" ref="dept"/> <!-- 属性为其它类型时 ref=进行配置 -->     
    </bean>

<!--
	此时就出现了以上的异常信息,原因容器不知道该注入哪一个bean。此时我们有如下的三种方式解决
-->	


   									 <!-- 延迟加载-懒加载 -->
    <bean id="dept" class="cn.qf.pojo.Dept" lazy-init="true"> <!-- 构造器注入 -->
        <constructor-arg index="0" value="10"/>
        <constructor-arg index="1" value="研发部门"/>
        <constructor-arg index="2" value="贵阳"/>
    </bean>
	
	<bean id="dept02" class="cn.qf.pojo.Dept"> <!-- set注入 -->
        <property name="dname" value="技术部门"></constructor-arg>
        <property name="deptno" value="10"></constructor-arg>
        <property name="loc" value="深圳"></constructor-arg>
    </bean>
  • 第一种方式:让其中一个Dept类型的bean优先注入
<!-- primaty="true"  优先注入 -->
<bean id="dept" class="cn.qf.pojo.Dept"  primaty="true"> <!-- 构造器注入 -->
        <constructor-arg index="0" value="10"/>
        <constructor-arg index="1" value="研发部门"/>
        <constructor-arg index="2" value="贵阳"/>
 </bean>
  • 第二种方式:指定其中一个自动退出注入(自动放弃注入)
<!-- autowire-candidate="false" -->
<bean id="dept02" class="cn.qf.pojo.Dept"  autowire-candidate="false"> 
    <!-- set注入 -->
        <property name="dname" value="技术部门"></constructor-arg>
        <property name="deptno" value="10"></constructor-arg>
        <property name="loc" value="深圳"></constructor-arg>
</bean>
  • 第三种方式:放弃使用根据类型自动注入,而是使用根据bean的名字注入
<!-- autowire="byName"  根据bean的名字注入-->
<bean id="emp" class="cn.qf.pojo.Emp" lazy-init="true" autowire="byName">
        <!--使用构造器注入的方式为对象的属性设置初始值-->
        <property name="empno" value="1001" />
        <property name="ename" value="兵兵"/>
        <property name="job" value="网关"/>
        <property name="sal" value="3000.0"/>
    <!-- 属性为其它类型时 ref=进行配置  引用 -->     
        <property name="4" ref="dept"/> 
 </bean>

<!-- 此时发现没有注入dept属性的信息,原因是现在使用的是根据名字进行注入,
要求Emp的Dept类型的属性的名称要和需要装配的bean的id保持一致。 -->
								 <!-- 延迟加载-懒加载 -->
 <bean id="dept" class="cn.qf.pojo.Dept" lazy-init="true"> <!-- 构造器注入 -->
        <constructor-arg index="0" value="10"/>
        <constructor-arg index="1" value="研发部门"/>
        <constructor-arg index="2" value="贵阳"/>
 </bean>

以上的操作就是自定义类型的自动注入方式,但是在开发中这种方式使用的不多,而是使用ref引用的方式多一点。

4.4 Spring日志的打印

​ 在学习mybaits的时候有日志打印的概念,需要下载对应的开发包,同时需要导入一个log4j.properties的资源文件, 现在也是一样需要对应的jar和资源文件。

<!-- spring日志打印的3个jar包 -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.21</version>
</dependency>

将log4j.properties拷贝到src/main/resources目录下

# Global logging configuration 开发时候建议使用 debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

4.5 延迟加载

​ 在默认的情况下bean是在服务启动的时候创建的,Spring在大部分分情况下都愿意选择一启动的时候就创建所有在配置文件中注册的类的对象,而不使用延迟加载(需要使用到对象的时候再创建)。

​ 启动的时候是我们自己等待,在使用的时候再去创建是客户在等待。

​ 所谓的延迟加载就是在第一次使用到bean的时候再创建对象,而不是一启动的时候立刻创建。作为知识点了解,因为一般在bean的创建不使用延迟加载。

<bean id="dept" class="cn.qf.pojo.Dept" lazy-init="true"></bean>

5.动态代理

静态代理有局限:只能代理Subject接口的实现类对象(一个代理类只能作为一个接口的代理),依赖于Subject接口,这种代码耦合度高,于是就出现了动态代理。

5.1 (jdk)动态代理的实现

代理主题类 implements InvocationHandler

要实现动态代理需要使用到两个类型分别是 “java.lang.reflect.Proxy” 和 “java.lang.reflect.InvocationHandler”。

/**
 * 真实主题类接口
 * */
public interface Subject {
    void sleepWithAifei();
}


/**
 * 真实主题类
 * */
public class EmperorA implements Subject{
    @Override
    public void sleepWithAifei() {
        System.out.println("皇上就寝WheneverWithAifei!");
    }
}


/**
 * 代理主题类 -- 需要实现 InvocationHandler 接口
 * */
public class WangGgProxy implements InvocationHandler {
    //主题类实例
    //private Object object;
    private Subject subject;

    public WangGgProxy() {
    }
    public WangGgProxy(Subject subject) {
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("1.给皇上洗澡"); //前置增强
                                    /* 1.this.object */
        Object result = method.invoke(this.subject,args);
        System.out.println("2.给皇上洗澡"); //后置增强
        return result;
    }
}


public class Test {
    public static void main(String[] args) {
        //创建真实主题类对象
        Subject emperor = new EmperorA();
        //创建代理主题类对象
        InvocationHandler wggProxy = new WangGgProxy(emperor);
        /**
         * 通过Proxy取得真实主体类对象的代理对象
         * 第一个参数:代理主题类的类加载器
         * 第二个参数:真实主题类的所有接口
         * 第三个参数:代理主题类实例对象
         * */
        //也可以转换为Proxy类型
        Subject proxyInstance = (Subject) Proxy.newProxyInstance(
                wggProxy.getClass().getClassLoader(),
                emperor.getClass().getInterfaces(),
                wggProxy
                );
        proxyInstance.sleepWithAifei();
    }
}
  • 要使用动态代理,一定要定义接口(jdk的动态代理)
  • 代理类要实现 InvocationHandler 接口
  • 为什么Proxy的newProxyInstance()方法创建的对象为什么能强制转换成Subject类型?
  • 为什么执行proxySubject的sleepWithAifei之后能执行到代理中的invoke方法呢?

5.2 动态代理之invoke方法(理解)

  1. 什么Proxy的newProxyInstance方法能返回Subject类型的对象?
  2. 为什么调用proxySubject的sleepWithAifei方法却最终会调用代理类的invoke方法

观察源码:

在这里插入图片描述

在这里插入图片描述

  • 以上的源码中:Class<?>cl=getProxyClass0(loader,intfs)取得的对象就是一个实现了Subject同时继承了Proxy的对象,所以我们能将newProxyInstance()方法取得对象强制转换为Subject
public class Test {

    public static void main(String[] args) {
        //创建真实主题类对象
        Subject emperor = new EmperorA();
        //创建代理主题类对象
        InvocationHandler wggProxy = new WangGgProxy(emperor);
        /**
         * 通过Proxy取得真实主体类对象的代理对象
         * 第一个参数:代理主题类的类加载器
         * 第二个参数:真实主题类的所有接口
         * 第三个参数:代理主题类对象
         * */
        Subject proxyInstance = (Subject) Proxy.newProxyInstance(
                wggProxy.getClass().getClassLoader(),
                emperor.getClass().getInterfaces(),
                wggProxy
                );
        proxyInstance.sleepWithAifei();
        System.out.println("代理对象proxyInstance的父类:"+proxyInstance.getClass().getSuperclass());
        System.out.println("代理对象proxyInstance的父接口:"+proxyInstance.getClass().getInterfaces()[0]);
    }
    /* 运行结果
    	代理对象proxyInstance的父类:class java.lang.reflect.Proxy
		代理对象proxyInstance的父接口:interface b动态代理.Subject
    */
}

  • proxySubject对象对应的类的名字是什么呢?其实上这个类的名称叫做$Proxy0,但是这个类是动态生成的,动态代理生成的$Proxy0.class是直接以二进制的方式加载进内存中的,并没有对应的.class文件生成,如果要观察到需要使用到代理生成器将对应的class文件生成到指定的文件夹,之后在通过反编译工具查看动态代理生成的代码。
@Test
    /**
     * 让proxyInstance代理实例对象: class生成对应的文件,则需要使用JDK的代理生成器实现
     * */
    public void test02() throws IOException {
        //创建真实主题类对象
        Subject subject = new EmperorA();
        //创建代理主题类对象
        InvocationHandler wggProxy = new WangGgProxy(subject);
        //取得代理类对象(该对象实现了Subject接口,同时继承了Proxy类)
        Subject proxyInstance = (Subject) Proxy.newProxyInstance(
                wggProxy.getClass().getClassLoader(),
                subject.getClass().getInterfaces(),
                wggProxy
        );
        //1.定义一个字节数组
        byte bts[]= ProxyGenerator.generateProxyClass("Proxy0",subject.getClass().getInterfaces());
        //2.实例化一个File对象
        File f = new File("F:"+File.separator+"Test_io"+File.separator+"Proxy0.class");
        //定义一个字节输出流对象
        FileOutputStream out = new FileOutputStream(f);
        //将class类输出到f对象对应的文件中
        out.write(bts);
        out.close();
    }

将以上的的class文件拷贝到项目中,使用反编译插件打开(如果是idea的话直接就可以打9开了,如果是eclipse的话要先安装反编译插件)·使用反编译插件打开文件

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

import b动态代理.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sleepWithAifei() throws  {
        try {
            //调用父类Proxy中的protectedInvocationHandlerh;
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("b动态代理.Subject").getMethod("sleepWithAifei");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

总结:

  • 通过Proxy.newProxyInstance()取得的对象本质上实现了Subject接口,所以可以将其强制转换为Subject类型,同时又继承了Proxy类。
  • 之所以能调用触发动态代理类(demo中的王公公类)中的invoke方法是因为在$Proxy中执行sleepWithAifei()方法的时候本质上是调用了父类(Proxy)中的h对象的invoke方法。

该类提供了方法创建代理类和代理类的对象的方法

创建一个代理类并返回代理类对象

static Object newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)

loader:类加载器,指定类加载器,是为了精确的定位类

interfaces:接口Class类,使用JDK的反射,必须要有接口

h:InvocationHandler,代理的处理器,每个代理类都有一个关联的处理器

JDK动态代理的不足在JDK中使用动态代理,必须有类的接口。因为生成的代理需要实现这个接口,这样我们生成的代理类对象,才能转化为代理目标的接口对象,然后根据接口中的方法,调用处理器中invoke方法。

在这里插入图片描述

6 Cglib动态代理(了解)

​ 为了弥补JDK动态代理的不足,第三方组织封装一套工具包,cglib的工具包,这套包不基于接口,基于父子继承,通过重写的形式扩展方法,但是这个子类工具自动生成的。早期,Cglib动态代理,性能相于JDK的动态代理高一些。JDK进行一些列优化,目前Spring默11认使用的动态代理JDK,也支持Cglib。

6.1 Cglib动态代理的使用

拦截类(代理类)实现implements MethodInterceptor:方法拦截器

​ cglib中,提供了对目标方法执行拦截的接口。其中intercept是对具体方法进行拦截处理的方法。

public Object intercept ( Object obj,java.lang.reflect.Method method,Object[]args,MethodProxy proxy)

Object:方法执行返回的结果

obj:增强类的对象

method:目标方法

proxy:用于回调的方法的对象

  • 导入jar包
<!-- Cglib动态代理 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
/**
 * 真实主题类
 * */
public class Emperor {
    public void sleepWithAifei(){
        System.out.println("sleeping with aifei.....");
    }
}


/**
 * 方法拦截器
 * */
public class MyMethodIntecept implements MethodInterceptor {
    @Override
    /**   为拦截的目标方法做增强处理的方法
     * @param o:被代理的对象
     * @param method: 被代理的对象的目标方法(sleepWithAifei)
     * @param objects:实际运行的参数(要传递的给目标方法的参数)
     * @param methodProxy: 代理对象
     * @return java.lang.Object
     **/
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("接爱妃来");
        Object result = methodProxy.invokeSuper(o,objects);
        System.out.println("送走爱妃");
        return result;
    }
}



/**
 * 测试类
 * */
public class Test_MethodIntecept {
    public static void main(String[] args) {
        //如果要观察到cglib动态代理生成的相关的class文件需要做如下配置
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,new File("").getAbsolutePath()+"/cglib");
        System.out.println(new File("").getAbsolutePath()+"/cglib");

        Enhancer enhancer = new Enhancer();
        //为代理对象设置父类
        enhancer.setSuperclass(Emperor.class);
        //设置回调(当调用代理类对象的sleepWithAifei就会回调方法拦截器中的intercept方法)
        enhancer.setCallback(new MyMethodIntecept());
        //取得动态代理生成的代理类对象
        Emperor emperor = (Emperor) enhancer.create();
        emperor.sleepWithAifei();
    }
}

总结:

1、cglib的动态代理不需要使用接口了

2、使用Enhancer类的create方法会生成代理类对象,代理类对象是继承真实主题类的。

7.AOP

Aspect Oriented Propramming 面向切面编程

​ 为了解决重复的代码,提出了一种开发思想:面向切面的编程,叫做AOP编程(AspectOrientedPropramming),就是将那些辅助的操作(打开连接、关闭连接、权限验证…)放到一个专门的类中实现,这个类可以横切需要操作数据的类的方法。

在这里插入图片描述

7.1 Spring的AOP配置

​ 接下来我们使用spring来实现aop编程,spring的aop本质就是动态代理设计,spring默认使用jdk动态代理,可以手工配置cglib的动态代理。

7.1.1 pom.xml中导入支持AOP的开发包
<!-- Spring支持AOP的开发包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>Spring-aop</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>
<!-- AOP织入处理的依赖 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.8</version>
    </dependency>
7.1.2 定义接口、实现类、切面类
/**
*接口
*/
public interface IService {
    void updateData();
    void deleteData();
}


/**
*  实现类
*  核心业务类定义在真实主题类中(点类)。
*/
public class ServiceImpl implements IService{
    @Override
    public void updateData() {
        System.out.println("主业务:数据更新操作!");
    }

    @Override 
    public void deleteData() {
        System.out.println("主业务:数据删除操作!");
    }
}


/**
* 定义切面类
*/
public class ServiceAOP {
    //在执行核心操作之前要执行的方法(同理Filter的前置增强/前置通知)
    public void beforeInvoke(){
        System.out.println("Connection数据库连接");
    }

    //执行核心业务之后要执行的方法
    public void afterInvoke(){
        System.out.println("关闭数据库连接!");
    }
}
7.1.3 需要在Spring的配置文件applicationContext.xml中增加aop的命名空间和约束
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
 


<!-- Spring的AOP配置Bean对象 -->
    <!-- 使用了自动注入则会从容器中查找一个和你的自定义的属性类型匹配的对象赋值-->
    <bean id="serviceImp" class="c_Spring_AOP.Dao.ServiceImpl"></bean>
    <!-- 注册切面的类对象 -->
    <bean id="serviceAop" class="c_Spring_AOP.ServiceAOP"></bean>

    <!-- AOP的配置-切面编程的配置 -->
    <aop:config>
        <!-- 配置切点:指定出要被切面处理的方法 -->
        <!-- expression="execution(*cn.service.impl..*.*(..))":
                表示cn.service.impl包下的所有方法都要被切面处理
                execution:执行、处决
                expression:表示、表达
        -->
        <aop:pointcut id="defaultPointcut" expression="execution(* c_Spring_AOP.Dao..*.*(..))"/>

        <!-- 配置切面 -->
        <aop:aspect ref="serviceAop">
            <!--指定要使用切面的那些方法-->
            <!--在执行目标方法之前要先执行切面中指定的beforeInvoke方法-->
            <aop:before method="beforeInvoke" pointcut-ref="defaultPointcut"></aop:before>
            <!--指定在执行目标方法之后要使用到的切面中的方法为afterInvoke()方法-->
            <aop:after method="afterInvoke" pointcut-ref="defaultPointcut"></aop:after>
        </aop:aspect>
    </aop:config>  
</beans>

<aop:pointcut id=“defaultPointcut” expression=“execution(*c_Spring的AOP配置.Dao…*.*(…))” />

execution:是一个Aspectj表达式

*:表示方法可以返回任意类型

c_Spring的AOP配置.Dao…:表示cn.mbzvip.service.impl下的任意类

*.*:表示任意类中的任意方法(所有方法)

(…):表示任意方法的任意参数

“execution(*cn.mbzvip.service.impl…*.*(…))”:整体表示切点的范围是cn.mbzvip.service.impl下的所有类的所有方法都是切点

7.1.4 测试类
public class TestAOP {
    public static void main(String[] args) {
        //加载spring的配置文件并且会自动生成一个spring运行的上下文环境(容器)所以被注册过的bean都在该容器中
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中取得需要的bean(根据对象的名字获取叫做byName)
        IService service = (IService) context.getBean("serviceImp");
        // System.out.println(dept);
        /**
         * 配置了AOP后通过类名.class获取Bean对象(代理对象)报错的原因?????
         * --because:代理类对象采用jdk动态代理时,他属于类【$Proxy0】所有根据类型.class获取bean就无法获取到
         * */
        //ServiceImpl service2 = (ServiceImpl) context.getBean(ServiceImpl.class);
        service.updateDate();
        service.deleteDate();
    }
}
  • 配置aop之前取得的就是DeptServiceImpl的普通对象
  • 配置aop之前取得的就是DeptServiceImpl类的代理对象

7.2 AOP第二讲【了解】

​ 我们配置两种AOP操作,分别是在执行目标方法之前织入辅助性的操作、一种是在执行完毕目标方法之后织入的辅助性操作,他们分别叫做前置通知和后置通知。除此之外还有三种,分别是:返回通知、抛出异常通知、环绕通知。

7.2.1 返回通知的配置

目标方法有返回值,就执行切面的方法。

/**
* 在切面中增加一个方法
* 处理返回通知的方法*@paramobj目标方法的返回值
*/
public void returnInvoke(Object obj){
	System.out.println("目标方法的返回值是:"+obj);
}
·到配置文件中增加配置
<!--
 	返回通知:
arg-names="obj":表示目标方法的参数名称(和目标方法的参数名一致)
returning="obj":返回的对象的名称也是obj(和目标方法的参数名字保持一致)
-->
<aop:after-returning  method="returnInvoke" arg-names="obj" returning="obj" pointcut-ref="defaultPointcut"></aop:after-returning>

我们可以在切面类中取得目标方法的返回值,那么可以在切面类中将返回值修改之后再返回到调用处(主方法)?

  • 是否可以改变返回值?

不能在切面类中将目标方法的返回值修改的,如果要修改我们后面可以使用一个叫做环绕通知的操作即可实现。

/**
* 不能实现修改返回值!!
*处理返回通知的方法
*@paramobj目标方法的返回值
*/
public boolean returnInvoke(Object obj){
    System.out.println("切面中取得目标方法的返回值是:"+obj);
    return true;
}
7.2.2 抛出异常通知

​ 如果在目标方法中抛出了异常则会触发我们指定切面类中的方法就是抛出异常通知,要在切面中增加一个方法throwExceptionInvoke()

<!--抛出异常通知-->
<aop:after-throwing method="throwExceptionInvoke" arg-names="e" throwing="e" pointcut-ref="defaultPointcut"></aop:after-throwing>
/**
*抛出异常通知
*@parame
*/
public void throwExceptionInvoke(Throwablee){
    System.out.println("在切面中取得目标方法抛出的异常:"+e);
}

packagecn.mbzvip.service.impl;
public class DeptServiceImpl{
    public boolean addDept(){
        //核心业务
        System.out.println("增加部门信息");
        int i=10/0;
        return true;
    }
    public boolean removeDept(){
        //核心业务
        System.out.println("删除部门信息");
        return false;
    }
}
7.2.3 环绕通知

​ 环绕通知类似于前置通知和后置通知的组合使用。

​ 在切面类的方法中需要使用到一个参数:ProceedingJoinPoint,可以使用该参数取得传递给目标方法的参数,还可以修改传递的参数之后在传递给目标方法,目标方法执行完毕之后会返回值,还可以在切面类中将返回的值修改,实现偷梁换柱的概念。

<!--配置环绕通知-->
<aop:around method="arrondInvoke" pointcut-ref="defaultPointcut"></aop:around>
packagecn.mbzvip.service.impl;
public class DeptServiceImpl{
    public boolean addDept(){
        //核心业务
        System.out.println("增加部门信息");
        inti=10/0;
        return true;
    }
    public boolean removeDept(){
        //核心业务
        System.out.println("删除部门信息");
        return false;
    }
    public boolean removeById(Integerid){
        System.out.println("根据编号删除数据成功");
        return true;
    }
}

/**
*环绕通知
*可以在该方法将目标的参数替换*@parampoint
*/
public boolean arrondInvoke(ProceedingJoinPoint point)throws Throwable{
    //取得传递给目标方法的参数
    System.out.println("在切面中取得目标方法的参数:"+Arrays.toString(point.getArgs()));
    boolean result=false;
    if((Integer)point.getArgs()[0]<0){
        System.out.println("你的编号不存在,即将给你的是默认值");
        result=(boolean)point.proceed(new Object[]{1001});
    }else{
        //执行目标方法
        result=(boolean)point.proceed(point.getArgs());
    }
    System.out.println("在切面中取得目标方法的返回值为:"+result);
    //在切面中修改了目标方法的返回值会出现调用出和目标方法的返回值不一样了
    result=false;
    return result;
}

7.3 使用注解实现AOP的配置

  • applicationContext.xml添加命名空间和约束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
  • 在配置文件中开启注解驱以及包扫描,还需要指定织入使用的JDK的动态代理方式还是使用CGLIB的代理实现方式。
<!--开启注解驱动也就是如果要使用注解的方式进行配置需要加上该节点-->
<context:annotation-config/>
<!--开启包扫描-->
<context:component-scan base-package="cn.mbzvip.service"/>
<!--开启组件扫描
    1、如果要扫描多个包,多个包用逗号隔开
    2、直接写到上层目录,所有类所有注解都扫描
-->
<context:component-scan base-package="java"></context:component-scan>
<!--
指定代理的方式默认使用的是jdk的动态代理 proxy-target-class="false"
现在使用的cglib的代理方式
-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
/**
 * 代理主题类/切面类
 * */
@Component("testAOP") //表示要使用该类创建一个对象到容器中等价之前
@Aspect  // 表示该类作为切面类
public class TestAOP {//就是之前的代理类(王gg角色)

    // 在执行核心操作之前要执行的方法
    @Before(value = "execution(* c_Spring_AOP.zjkf.Main..*.*(..))")
    public void beforeInvoke() {
        System.out.println("取得连接!");
    }
    //执行核心业务之后要执行的方法
    @After(value="execution(* c_Spring_AOP.zjkf.Main..*.*(..))")
    public void afterInvoke(){
        System.out.println("关闭数据库连接!");
    }
}

/**
 * 真实主题类/切点类
 * */
@Component("userTest")
public class TestUser  {

    public void mainMethod(){
        System.out.println("进行数据的操作!");
    }
}


/**
 * 测试类
 * */
public class Test_注解开发 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //Proxy testAOP = (Proxy) applicationContext.getBean("testAOP");
        /**
         * 注解开发:使用Cglib进行动态代理:
         * -在切点类(真实主题类)没有实现任何接口时,spring-AOP 默认使用Cglib(applicationContext.xml配置jdk)
         * -Cglib获取的代理对象所属的类继承于切点类(真实主题类),所有在获取代理类对象bean时,是获取切点类的bean
         * */
        TestUser userTest = (TestUser) applicationContext.getBean("userTest");
        userTest.mainMethod();
    }
}
  • 注解开发使用的是Cglib
  • 真实业务类(切点类)实现接口时,默认使用cglib(配置文件修改无效)
  • Cglib获取的代理对象所属的类继承于切点类(真实主题类),所有在获取代理类对象bean时,是获取切点类的bean

​ 以上所有操作就是AOP的配置,在之后我们与mybatis结合之后还有另外一种配置方式,但是原理都差不多,只要你现在的会了后面就没问题。

7.4 代理类配置

在这里插入图片描述

JDK的原生代理和CGLIB的代理有什么不同?

  • jdk只能处理实现了接口的实现类,也就是要求目标类必须要有自己的接口(这就是它的不足)。
  • CGLIB不要求目标必须要有自己的接口,它的出现就是为了解决jdk原生的代理的缺陷
<aop:aspectj-autoproxy/>
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为
<aop:aspectj-autoproxypoxy-target-class="true"/>时,表示使用cglib动态代理技术织入增强。
不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

8. 任务调度

​ 在学习数据库的时候我们曾经说过一句话:“可以在夜间访问流量小的时候进行数据的汇总”比如每个月的1号的凌晨1点要进行上个月的财务统计。

8.1实现任务定时调度

  • 启动是调用其它线程时会自动启动定时任务,不需要单独获取其bean对象来执行任务

  • 需要在配置文件的头部增加相关的信息

xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.1.xsd">
  • applicationContext配置文件中配置任务工具执行任务的行为
<!--注册任务调度的bean-->
<bean id="task01" class="d_任务调度配置.Task_a"/>
<bean id="task02" class="d_任务调度配置.Task_b"/>

<task:scheduled-tasks>
        <!--方式一-->
        <task:scheduled ref="task01" method="run" fixed-rate="2000"/>
        <!--方式二-->
        <task:scheduled ref="task02" method="run" cron="* 50 17 1 * ?"/>
    </task:scheduled-tasks>

<!--方式一:配置任务执行的行为-->
<task:scheduled-tasks>
    <task:scheduled ref="task_a" method="run" fixed-rate="2000"/>
</task:scheduled-tasks>
<!--
	ref="task_a":定义执行任务的bean对象
	bean·method="run":执行任务的方法
	fixed-rate="2000":表示每隔两秒钟执行一次任务
-->

<!--方式一:配置任务执行的行为-->
<task:scheduled-tasks>
	<task:scheduled ref="对象id" method="方法名称" cron="*****?"/>
</task:scheduled-tasks>
<!--
	1.cron="*****?":表示每一年中的每秒执行一次
	2.cron="***1*?":表示每一年每月的1号的每一秒都执行一次任务
	3.cron="0001*?":表示每一年的每月的一号的0时0分0秒执行任务
	4.cron="003**?":表示每一年的每月每日凌晨3点0分0秒执行任务
	5.cron="0031*?":表示每一年的每月一号凌晨3点0分0秒执行任
-->
public class Task_a {
    public void run(){
        String simpleDateFormat_date = new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date());
        System.out.println(simpleDateFormat_date+":调度任务a执行。。。。");
    }
}


public class Task_b {
    public void run(){
        String simpleDateFormat_date = new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date());
        System.out.println(simpleDateFormat_date+":调度任务b执行。。。。");
    }
}

​ 以上的执行结果发现了B任务要等待A任务执行完毕之后才能执行,因为默认情况下同一时间只能一个任务执行,如果要多个任务执行需要配置为异步。

8.2 异步任务调度

<!--同一个时间点可以执行20个任务-->
<task:scheduler id="scheduler" pool-size="20"></task:scheduler>
<!--配置任务执行的行为-->
<task:scheduled-tasks>
    <task:scheduled ref="task_a" method="run" cron="*****?"/>		
</task:scheduled-tasks>
<task:scheduled-tasks>
    <task:scheduled ref="task_b" method="run" cron="*****?"/></task:scheduled-tasks>

​ 以上配置之后就A和B执行的时候不会互相等待,各自执行但是以上的配置方式是在配置文件中实现,还可以使用注解配置。

8.3 注解实现配置

<!--开启任务调度的注解驱动支持-->
<task:annotation-driven/>
<!--开启包扫描-->
<context:component-scan base-package="cn.mbzvip"/>
@Component
public class Task_c {
    @Scheduled(cron ="* * * * * ?")
    public void run(){
        String simpleDateFormat_date = new SimpleDateFormat("yy-MM-dd hh:mm:ss").format(new Date());
        System.out.println("注解实现任务调度--时间:"+simpleDateFormat_date);
    }
}

9. 事务Transaction

9.1 事务的概念

​ 事务是访问数据库的一个操作序列(简单的说就是一次程序的执行会发送多条sql语句),数据库应用系统通过事务集(多个事务)来完成对数据的存取。事务的正确执行使得数据库从一种状态转换成另一种状态。

​ 事务的单位怎么区分?用jdbc来说,一个连接中所有操作就是一个事务(一个会话),用mybatis来说,一个SqlSession中的所有操作就是一个事务(也叫做会话)

9.2 事务的特征

如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性:四大特性(简称ACID)

  • 原子性(Atomicity[ˌætəmˈɪsɪti])

      一次事务中如果进行多个数据操作的业务,最终的结果要么所有操作都成功,要么所有操作都失败  eg:A转账,B收到帐
    
  • 一致性(Consistency[kənˈsɪstənsi]):

       一致性就是数据表中的数据更新要求合乎逻辑的特性,满足了原子性不一定满足一致性。 eg:A转账100,B只收到了50
    
  • 隔离性(Isolation[ˌaɪsəˈleɪʃn])

      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离(数据库的底层是通过锁机制实现)。有点像Java的互斥锁(Java的多线程哪里分析的)
    
  • 持久性(Durability[ˌdjʊərə’bɪlətɪ])

      持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。[DBMS、容灾处理]
    

9.3 事务的隔离性

​ 当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个事务获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果事务没有隔离性会发生的几种问题:

  • 脏读 :脏读是指在一个事务处理过程中读取了另一个事务未提交的数据(即将回滚的数据)
  • 不可重复读

​ 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于上一次和下一次在查询间隔期间,数据被另一个事务修改并提交了,导致两次查询到的数据不一样。

​ 不可重复读和脏读的区别是:脏读是某一事务读取了另一个事务未提交(即将被回滚)的脏数据,而不可重复读则是读取了前一事务提交的数据。

​ 在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主,也就是在不严格的情况下这不算什么问题。

  • 虚读(幻读)

      幻读是事务没有隔离性时发生的一种现象。比如A事务在统计数据量的时候查询到数据为100条数据,在这期间B事务插入了一条数据,变成了101。
    
      幻读和不可重复读都是读取了另一条已经提交的事务的数据(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
    

四种隔离级别:

①Readuncommitted(读未提交隔离级别):最低级别,任何情况都无法保证。

②Readcommitted(读已提交隔离级别):可避免脏读的发生。③Repeatableread(可重复读隔离级别):可避免脏读、不可重复读的发生。④Serializable(串行化隔离级别):可避免脏读、不可重复读、幻读的发生。

在这里插入图片描述

​ 以上四种隔离级别最高的是Serializable级别,最低的是Readuncommitted级别,当然级别越高,执行效率就越低。

​ 像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。

查看当前事务的隔离级别:select @@tx_isolation

将事务的隔离级别设置为Read uncommitted级别:

set transaction isolation level repeatable read;

set tx_isolation = ’ read-unconmmitted ’

设置数据库的隔离级别一定要是在开启事务之前!

​ 在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatableread(可重复读);而在Oracle数据库中,只支持Serializable(串行化)级别和Readcommitted(读已提交)这两种级别,其中默认的为Readcommitted级别。

10. Spring事务的配置与传播行为

​ 事务传播行为(propagationbehavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。

​ 例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

事务设置的命名空间和约束

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd

事务的依赖

<!--导入事务的依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.13</version>
</dependency>
<!--配置切点的事务处理行为-->
<tx:Empid="txEmp" transaction-manager="transactionManager">		<tx:attributes>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="add*" propagation="REQUIRED"/>
        <tx:method name="remove*" propagation="REQUIRED"/>
        <tx:method name="rm*" propagation="REQUIRED"/>
        <tx:method name="edit*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="change*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="get*" propagation="REQUIRED"/>
        <tx:method name="load*" propagation="REQUIRED"/>
        <tx:method name="check*" propagation="REQUIRED"/>
        <tx:method name="select*" propagation="REQUIRED"/>
        <tx:method name="login*" propagation="REQUIRED"/>
        <tx:method name="*"  propagation="REQUIRED"/>
    </tx:attributes>
</tx:Emp>

Spring中的事务有七种传播行为;

1、PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

2、PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

3、PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常,调用的地方必须开启事务,否则抛出异常。

4、PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作(如果外层事务中出现了异常则内层的事务要回滚,如果嵌套的事务出现了异常则外层的事务不受到影响不会回滚)。在springBoot中我们还会详细去分析的。常用的是PROPAGATION_REQUIRED

11.Spring面向注解开发annotation

开启注解驱动的支持和包扫描

<!--开启注解配置的支持-->
<context:annotation-config/>
<!--包扫描配置-->
<context:component-scan base-package="cn.qf"/>

注解的使用

  • @Component(“empService”):表示要创建一个bean到容器中(建议在dao层或者其他组件中使用)默认的名字就是类名的手写字母小写。
  • @Scope(“prototype”)//表示使用多例形式创建bean
  • @Lazy//懒加载
  • @Service//创建一个bean到容器中(建议在业务层使用)
  • @Controller//创建一个bean到容器中(建议在控制层中使用,表示一个控制器)。{@RestController}
  • @Repository//创建一个bean到容器中(建议在dao层使用)
  • @Configuration//创建一个bean到容器中(建议在配置类上使用)
  • @ComponentScan(“–”) //开启包扫描
  • @Autowired//自动注入:要在容器中查找对应的bean复制给对应的属性,如果容器中没有则抛出异常。
  • @Bean//方法上使用 @Bean注解会将该方法创建的对象添加到Spring容器中

@Autowired//在容器中查找一个和该类匹配的bean注入(byType)23

@Resource(name=“messageSource”)//在容器中查找一个名字叫做messageSource的bean注入给该属性(byName)@Resource(type=ResourceBundleMessageSource.class)//在容器中查找一个类型为ResourceBundleMessageSource的bean注入给该属性(byName)

package e_Spring面向注解开发;
public class Annotations注解 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        /*System.out.println(applicationContext.getBean("annotation_a"));
        System.out.println(applicationContext.getBean("annotation_a"));
        System.out.println(applicationContext.getBean("annotation_a"));*/

        //System.out.println(applicationContext.getBean("annotation_b"));

        //System.out.println(applicationContext.getBean("annotation_c"));

        //System.out.println(applicationContext.getBean("annotation_d"));

        //System.out.println(applicationContext.getBean("annotation_e"));

        /*Annotation_Autowired a = new Annotation_Autowired();
        System.out.println(a);*/

        Annotation_Autowired a = (Annotation_Autowired) applicationContext.getBean("annotation_Autowired");
        a.CreatBean();
        /**
         * @Autowired: 自动注入注解失效--属性为null ?????
         *      ---解决:注入对象当前所属类也需要生成bean对象
         * */
        System.out.println("@Autowired自动注入注解获取的属性:"+a.getAnnotation_b());
        /**
         * 如何找到@Bean注解在方法产生的对象 --找不到???
         *      --- 1.解决方法:@Bean(”设置名称!“)获取时直接名称获取
         *      --- 2.使用返回值类型.class方式获取bean对象
         * */
         System.out.println("@Bean注解在方法中产生的对象保存在ioc容器中:"+applicationContext.getBean(String.class));

    }

}
@Component //创建bean 建议在dao层或者其他组件中使用
@Scope("prototype") //表示使用多例形式创建bean
@Lazy//懒加载
class Annotation_a{}

@Service //创建bean(建议在业务层使用)
class Annotation_b{
    String msg ="hhhh";
}

@Controller //创建一个bean建议在控制层中使用
class Annotation_c{}

@Repository //创建一个bean(建议在dao层使用)
class Annotation_d{}

@Configuration //创建一个bean(建议在配置类上使用)
class Annotation_e{}

@Component
class Annotation_Autowired{
    /**
     * 自动注入注解需要当前所属类也需要生成bean对象保存在ioc容器中
     * */
    @Autowired
    private Annotation_b annotation_b;

    @Override
    public String toString() {
        return "Annotation_Autowired{" +
                "annotation_b=" + annotation_b +
                '}';
    }
    @Bean("string")
    public String CreatBean(){
        System.out.println("@Bean注解,在方法中产生的对象存入IOC容器中");
        return new String("@Bean注解保存的bean对象");
    }
    public Annotation_b getAnnotation_b() {
        return annotation_b;
    }
    public void setAnnotation_b(Annotation_b annotation_b) {
        this.annotation_b = annotation_b;
    }
}

/*
输出:
@Bean注解,在方法中产生的对象存入IOC容器中
 INFO [main] - No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
@Bean注解,在方法中产生的对象存入IOC容器中
@Autowired自动注入注解获取的属性:e_Spring面向注解开发.Annotation_b@723ca036
22-11-02 11:12:15:调度任务a执行。。。。
@Bean注解在方法中产生的对象保存在ioc容器中:@Bean注解保存的bean对象
	
*/

12. Spring的JDBC概念

​ 在之前数据层有原始的JDBC进行操作,之后学习了Mybatis进行了jdbc的简化操作,Spring也提供了自己的jdbc操作,就是对原始的jdbc进行简单的封装。

12.1 Sprin-JDBC的配置

  • 下载Spring-jdbc的开发包
<!-- Spring的jdbc模块 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>
    <!--数据库连接驱动包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.43</version>
    </dependency>

  • 在Spring的主配置文件中配置数据源(dataSource)

      我们在原始的jdbc中取得连接对象使用的是**DriverManger**类,在Mybatis中使用**SqlSessionFactory**取得,在Spring的jdbc中使用**DriverManagerDataSource**
    
<!--
 配置数据源bean,可以使用数据源对象取得连接--bean对象
    class:在java文件中导入DriverManagerDataSource查看详细地址
    set注入:属性值查看DriverManagerDataSource类中的属性或者其父类、接口的属性
-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="Lcj1024.."></property>
        <property name="url"
                  value="jdbc:mysql://192.168.226.128:3306/TestLiu?useSSL=false&amp;Unicode=true&amp;characterEncoding=UTF-8">
        </property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>
  • 在主方法中获取连接Connection
public class Spring_jdbc_test01 {
    public static void main(String[] args) throws SQLException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource)applicationContext.getBean("dataSource");
        //获取Bean对象DriverManagerDataSource实例
        System.out.println(dataSource);
        //通过DriverManagerDataSource实例获取连接
        System.out.println(dataSource.getConnection());
    }
}

/* 日志log
org.springframework.jdbc.datasource.DriverManagerDataSource@5b38c1ec
DEBUG [main] - Creating new JDBC DriverManager Connection to [jdbc:mysql://192.168.226.128:3306/Test?useSSL=false&Unicode=true&characterEncoding=UTF-8]
com.mysql.jdbc.JDBC4Connection@102cec62

*/

​ 以上可以取得连接,但是这种形式在开发中是不能使用的,因为每次访问数据库都要重新取得连接,使用完毕之后要立刻关闭,这种是很浪费时间性能的,此时需要使用数据库连接池来解决这一问题。

12.2 配置C3P0连接池

​ C3p0只是其中的一个连接池工具,是比较传统经典的一款连接池工具,很多项目还在使用,新的项目用Druid就行了。

  • 下载连接池依赖(开发包)
<!--一下是C3p0连接池的开发包配置-->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>

    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>mchange-commons-java</artifactId>
      <version>0.2.12</version>
    </dependency>
  • 配置数据源,但是此时不再是Spring的JDBC的DrvierManagerDataSource对象了,而是使用C3P0的CombopooledDataSource类作为数据源对象
<!--
配置数据源bean,可以使用数据源对象取得连接
 -C3P0的CombopooledDataSource类作为数据源对象
 -java类中导入,获取类的package包地址
 -java类中导入,查看类中、父类、接口查找所含的属性,set注入
-->
    <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl"
                  value="jdbc:mysql://192.168.226.128:3306/Test?useSSL=false&amp;Unicode=true&amp;characterEncoding=UTF-8">
        </property>
        <property name="user" value="root"></property>
        <property name="password" value="Lcj1024.."></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <!--连接池中可以保存的最大连接数据量-->
        <property name="maxPoolSize" value="100"></property>
        <!--连接池中保存的最小连接数-->
        <property name="minPoolSize" value="10"></property>
        <!--初始化连接数-->
        <property name="initialPoolSize" value="10"></property>
        <!--表示某个连接如果在10秒之内没有被使用则销毁-->
        <property name="maxIdleTime" value="10000"></property>
    </bean>
  • 测试方法:
public class Spring_jdbcTest {
    public static void main(String[] args) throws SQLException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        /*DataSource dataSource = (DataSource)applicationContext.getBean("dataSource");
        //获取Bean对象DriverManagerDataSource实例
        System.out.println(dataSource);
        //通过DriverManagerDataSource实例获取连接
        System.out.println(dataSource.getConnection());*/

        //c3p0获取连接Connection
        //获取数据源对象bean
        DataSource c3p0DataSource = (DataSource)applicationContext.getBean("c3p0DataSource");
        //通过源对象获取c3p0连接池连接
        System.out.println("连接对象:"+c3p0DataSource.getConnection());
    }
}

/*输出结果:
连接对象:com.mchange.v2.c3p0.impl.NewProxyConnection@68034211
*/

12.3 使用资源文件保存连接信息

  • 在配置文件中引用资源文件
<!--指定资源文件的地址-->
<context:property-placeholder location="classpath:dataSource.properties"/>
  • 创建dataSoutce.properties 文件
jdbc.jdbcUrl=jdbc:mysql://192.168.226.128:3306/Test?useSSL=false&amp;Unicode=true&amp;characterEncoding=UTF-8
jdbc.user=root
jdbc.password=Lcj1024..
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.maxPoolSize=100
jdbc.minPoolSize=10
jdbc.initialPoolSize=20
jdbc.maxIdleTime=10000
  • 修改主配置文件
<!-- 指定资源文件地址 -->
    <context:property-placeholder location="classpath:dataSource.properties"></context:property-placeholder>
    <!-- 修改主配置文件 -->
    <bean id="c3p0DataSource02" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl"
                  value="${jdbc.jdbcUrl}">
        </property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <!--连接池中可以保存的最大连接数据量-->
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
        <!--连接池中保存的最小连接数-->
        <property name="minPoolSize" value="${jdbc.minPoolSize}"></property>
        <!--初始化连接数-->
        <property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
        <!--表示某个连接如果在10秒之内没有被使用则销毁-->
        <property name="maxIdleTime" value="${jdbc.maxIdleTime}"></property>
    </bean>
  • 测试
public class Spring_jdbcTest {
    public static void main(String[] args) throws SQLException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        /*DataSource dataSource = (DataSource)applicationContext.getBean("dataSource");
        //获取Bean对象DriverManagerDataSource实例
        System.out.println(dataSource);
        //通过DriverManagerDataSource实例获取连接
        System.out.println(dataSource.getConnection());*/

        //c3p0获取连接Connection
        //获取数据源对象bean
        DataSource c3p0DataSource = (DataSource)applicationContext.getBean("c3p0DataSource02");
        //通过源对象获取c3p0连接池连接
        System.out.println("连接对象:"+c3p0DataSource.getConnection());
    }
}

/*
测试结果:连接对象:com.mchange.v2.c3p0.impl.NewProxyConnection@46271dd6
*/

12.4 Spring的jdbc操作数据 JdbcTemplate

​ Spring提供了自己对原始的jdbc简单的封装操作,但是如果要实现数据的操作需要使用到一个叫做JdbcTemplate的一个类,但是Spring的jdbc操作个人感觉不是很好使用。

​ 实现数据的更新(增加、修改、删除)需要在配置文件中注册JdbcTemplate类

<!-- (Spring-jdbc的数据库操作对象)jdbcTemplate的bean注册 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="c3p0DataSource02">
    </property>
 </bean>

在主方法中使用jdbcTemplate进行数据更新

package f_SpringJDBC.test;
import f_SpringJDBC.pojo.Emp;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import java.sql.*;
import java.util.List;
/**
 * 在主方法中使用jdbcTemplate进行数据更新
 */
public class JdbcTemplateTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
        //System.out.println("获取到的JdbcTemplate对象:"+jdbcTemplate);
        //addEmp(jdbcTemplate);
        //addEmpReturnKey(jdbcTemplate);
        findAllSplit(jdbcTemplate);
    }
    /**
     * 插入Emp数据的方法
     *
     * @param jdbcTemplate:
     **/
    public static void addEmp(JdbcTemplate jdbcTemplate) {
        //sql语句
        String sql = "INSERT INTO emp(id,`name`,deptid,gender,salary)" +
                "VALUES(?,?,?,?,?)";
        //插入数据
        int row = jdbcTemplate.update(sql, 10, "小刘", 1, "男", 1245.12);
        System.out.println("插入数据影响行数:" + row);
    }
    /**
     * insert增加数据。返回主键值
     *
     * @param jdbcTemplate:
     **/
    public static void addEmpReturnKey(JdbcTemplate jdbcTemplate) {
        //用来操作自增长主键值的对象
        KeyHolder generateKeyHolder = new GeneratedKeyHolder();
        //如果一个接口中只有一个抽象方法则该接口叫做函数式接口(java基础部分匿名内部类那里分析的)
        String sql = "INSERT INTO emp(`name`,deptid,gender,salary)" +
                "VALUES(?,?,?,?)";
        /*
         update-传入两个参数
         1.函数式接口
         2.接收返回主键值的GeneratedKeyHolder实例
        */
        int row = jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                //获取预编译对象,并返回主键值的操作
                PreparedStatement prepareStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                //传入values值
                prepareStatement.setString(1, "小苏");
                prepareStatement.setInt(2, 4);
                prepareStatement.setString(3, "女");
                prepareStatement.setDouble(4, 6200.15);
                return prepareStatement;
            }
        }, generateKeyHolder);
        System.out.println("insert插入数据影响行数:" + row);
        System.out.println("插入数据的主键值GenerateKeyHolder-id:" + generateKeyHolder.getKey());
    }
}

​ 在之前使用mybatis的时候可以获取到自增长的主键值,我们也可以使用JdbcTemplate实现同样的操作。

​ 实现数据的模糊分页查询

 /**
     * 实现模糊分页查询emp
     *
     * @param jdbcTemplate:
     */
    public static void findAllSplit(JdbcTemplate jdbcTemplate) {
        int cp = 2; //表示当前页
        int ls = 5;//每页显示的数据量
        //sql语句
        String sql = "SELECT * FROM emp WHERE name LIKE ? LIMIT ?,?";
        //RowMapper<Emp> 是一个函数式接口
        List<Emp> empList = jdbcTemplate.query(sql, new Object[]{"%l%", (cp - 1 ) * ls, ls}, new RowMapper<Emp>() {
            @Override
            public Emp mapRow(ResultSet resultSet, int i) throws SQLException {
                Emp emp = new Emp();
                emp.setId(resultSet.getInt(1));
                emp.setName(resultSet.getString(2));
                emp.setDeptid(resultSet.getInt(3));
                emp.setGender(resultSet.getString(4));
                emp.setSalary(resultSet.getDouble(5));
                return emp;
            }
        });
        System.out.println(empList);
    }

​ 以上的代码就是Spring的jdbc操作,关于这个知识点,虽然不是很好用,有公司还在使用。

13.注解配置JDBC+事务

​ 之前使用了注解,但是还是继续使用一部分的配置文件,也就是没有完全使用注解,这节课我们来实现所有的配置都用注解来实现。

  • 配置类
package g_注解配置_JDBC_事务.config;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration//表示该类是一个配置类
@EnableTransactionManagement //开启事务管理支持(必须要有事务管理器)
@ComponentScan("g_注解配置_JDBC_事务") //开启包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //使用cglib代理
public class AppConfig {
    /**
     * Spring提供了自己对原始的jdbc简单的封装操作,
     * 但是如果要实现数据的操作需要使用到一个叫做JdbcTemplate的一个类
     * @return org.springframework.jdbc.core.JdbcTemplate
     **/
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }

    /**
     * Spring-jdbc提供的事务管理器
     * transaction-事务
     * @return org.springframework.transaction.PlatformTransactionManager
     **/
    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }

    /**
     * 配置数据源对象(Spring-jdbc提供DriverManagerDataSource)
     *
     * @return DataSource
     **/
    @Bean
    public DataSource dataSource() {
        //使用Spring-jdbc的DriverManagerDataSource
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setUrl("jdbc:mysql://192.168.226.128:3306/TestLiu?useSSL=false&amp;Unicode=true&amp;characterEncoding=UTF-8");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setPassword("Lcj1024..");
        return driverManagerDataSource;
    }
}
  • 测试类
package g_注解配置_JDBC_事务.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional //开启事务
@Service  //创建bean到容器中(建议在业务层使用)
public class EmpServiceImpl {
    public void test(){
        System.out.println("测试!");
    }
}


package g_注解配置_JDBC_事务.test;
import g_注解配置_JDBC_事务.config.AppConfig;
import g_注解配置_JDBC_事务.service.EmpServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class TestAnnotation {
    public static void main(String[] args) {
        //加载配置类并且会自动生成一个spring运行的上下文环境(容器)所以被注册过的bean都在该容器中
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

       /* JdbcTemplate jdbcTemplate =
                (JdbcTemplate)annotationConfigApplicationContext.getBean("jdbcTemplate");
        System.out.println(jdbcTemplate);*/

        EmpServiceImpl empService =
                (EmpServiceImpl) annotationConfigApplicationContext.getBean("empServiceImpl");
        empService.test();
    }
}

14 事务的传播行为测试

Spring中的事务有七种传播行为;

  • config配置类( @ConponentScan注解扫描多个包 )
package g_注解配置_JDBC_事务.config;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration//表示该类是一个配置类
@EnableTransactionManagement //开启事务管理支持(必须要有事务管理器)
@ComponentScan({"g_注解配置_JDBC_事务","事务的传播行为"}) //开启包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //使用cglib代理
public class AppConfig {
    /**
     * Spring提供了自己对原始的jdbc简单的封装操作,
     * 但是如果要实现数据的操作需要使用到一个叫做JdbcTemplate的一个类
     * @return org.springframework.jdbc.core.JdbcTemplate
     **/
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }

    /**
     * Spring-jdbc提供的事务管理器
     * transaction-事务
     * @return org.springframework.transaction.PlatformTransactionManager
     **/
    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }

    /**
     * 配置数据源对象(Spring-jdbc提供DriverManagerDataSource)
     *
     * @return DataSource
     **/
    @Bean
    public DataSource dataSource() {
        //使用Spring-jdbc的DriverManagerDataSource
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setUrl("jdbc:mysql://192.168.226.128:3306/TestLiu?useSSL=false&amp;Unicode=true&amp;characterEncoding=UTF-8");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setPassword("Lcj1024..");
        return driverManagerDataSource;
    }
}

/**
* 执行方法的测试类
*/
public class Transaction_Propagation_Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext(AppConfig.class);
        Emp_Transaction service = (Emp_Transaction) config.getBean("emp_Transaction");
        
        service.add();
    }
}
  • propagation 传播、繁衍

1、PROPAGATION_REQUIRED:(必须的、规定的、理想的)如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择(如果调用端发生了异常则调用端和被调用端都要回滚)

package 事务的传播行为.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service //加载创建bean对象(建议Service层使用)
public class Emp_Transaction {
    @Autowired //自动注入对象
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private Emp_Transaction emp_Transaction;

    @Transactional(propagation= Propagation.REQUIRED)
    public void add(){
        String sql = "INSERT INTO emp(id,name,deptid,gender,salary) VALUES(?,?,?,?,?)";
        int row = jdbcTemplate.update(sql,11,"苏苏",1,"女",7000.02);
        emp_Transaction.addDept();
    }
    @Transactional(propagation=Propagation.REQUIRED)
    public void addDept(){
        String sql = "INSERT INTO dept(id,name,address) VALUES(?,?,?)";
        int row = jdbcTemplate.update(sql,5,"千峰","贵阳");
    }
}

​ 如果内部方法和外部方法的传播行为都是REQUIRED则内部方法出现了异常,内部和外部方法都要回滚。外部方法出现异常,也会导致内部方法和外部方法都回滚

2、PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行

3、PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常,调用的地方必须开启事务,否则抛出异常。

4、PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起(事务具备隔离性)。

package 事务的传播行为.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service //加载创建bean对象(建议Service层使用)
public class Emp_Transaction {
    @Autowired //自动注入对象
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private Emp_Transaction emp_Transaction;

    @Transactional(propagation= Propagation.REQUIRED)
    public void add(){
        String sql = "INSERT INTO emp(id,name,deptid,gender,salary) VALUES(?,?,?,?,?)";
        int row = jdbcTemplate.update(sql,11,"苏苏",1,"女",7000.02);
        emp_Transaction.addDept();
        //int i = 10/0;
    }
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void addDept(){
        String sql = "INSERT INTO dept(id,name,address) VALUES(?,?,?)";
        int row = jdbcTemplate.update(sql,5,"千峰","贵阳");
        int i = 10/0;
    }
}

​ 内部方法出异常会导致内部和外部回滚,外部方法出问题则外部回滚,内部不回滚。

5、Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

package 事务的传播行为.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service //加载创建bean对象(建议Service层使用)
public class Emp_Transaction {
    @Autowired //自动注入对象
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private Emp_Transaction emp_Transaction;

    @Transactional(propagation= Propagation.REQUIRED)
    public void add(){
        String sql = "INSERT INTO emp(id,name,deptid,gender,salary) VALUES(?,?,?,?,?)";
        int row = jdbcTemplate.update(sql,11,"苏苏",1,"女",7000.02);
        emp_Transaction.addDept();
        int i = 10/0;
    }
    @Transactional(propagation=Propagation.NOT_SUPPORTED)
    public void addDept(){
        String sql = "INSERT INTO dept(id,name,address) VALUES(?,?,?)";
        int row = jdbcTemplate.update(sql,5,"千峰","贵阳");
        //int i = 10/0;
    }
}

​ 外部方法出异常,外部回滚,内部不回滚(非实物方法永远提交),内部方法出异常则内部不回滚(非实物方法永远提交),外部回滚(外部的事务只是挂起,内部方法执行完毕之后还是以事务的方式去运行的也会收到异常的影响!)。

6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

package 事务的传播行为.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service //加载创建bean对象(建议Service层使用)
public class Emp_Transaction {
    @Autowired //自动注入对象
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private Emp_Transaction emp_Transaction;

    @Transactional(propagation= Propagation.REQUIRED)
    public void add(){
        String sql = "INSERT INTO emp(id,name,deptid,gender,salary) VALUES(?,?,?,?,?)";
        int row = jdbcTemplate.update(sql,11,"苏苏",1,"女",7000.02);
        
    /**
     * 当外部方法中直接调用(this)addDept()就不会报错,因为调用该外部方法的是代理对象(事务管理+代理),代理对象继承了该本类,代理对象增强后去调用外部方法,其中外部方法里包含的内部方法就不会被增强处理,所有就不存在事务
     *
     * 当使用 emp_Transaction.addDept()
     */
        emp_Transaction.addDept();
    }
    @Transactional(propagation=Propagation.NEVER)
    public void addDept(){
        String sql = "INSERT INTO dept(id,name,address) VALUES(?,?,?)";
        int row = jdbcTemplate.update(sql,5,"千峰","贵阳");
    }
}

7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作(如果外层事务中出现了异常则内层的事务要回滚,如果嵌套的事务出现了异常则外层的事务不受到影响不会回滚)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值