bingmous

欢迎交流,不吝赐教~

导航

Spring5学习笔记(整理)

涉及的spring模块:

  • spring-context(包含了aop、bean、core、expression)
  • spring-aspects
  • spring-orm(包含了spring-jdbc、spring-tx)

Spring框架概述

  • Spring是轻量级的开源的JavaEE框架
  • Spring可以解决企业应用开发的复杂性
  • Spring有两个核心部分:IOC和AOP
    • IOC(Inversion of Control),控制反转,把创建对象过程交给Spring进行管理
    • AOP(Aspect Oriented Programming),面向切面编程,不修改源代码进行功能增强
  • Spring特点:
    • 方便解耦,简化开发
    • AOP编程支持
    • 方便程序测试
    • 方便和其他框架整合
    • 方便进行事务操作
    • 降低API开发难度

Spring源码设计精妙,是提高Java编程技术的经典学习范例。

官网:https://spring.io/

jar包和源码下载地址:在官网选择Spring Framework,点击右上角GitHub图标,进入GitHub源码地址,在Access to Binaries,进入Spring Framework Artifactswiki page,最下面Downloading a Distribution,进入Spring仓库https://repo.spring.io,选择Artifacts -> release/org/springframework/spring/即可找到对应版本

或者直接进入:https://repo.spring.io/release/org/springframework/spring/

入门案例

创建maven工程,引入spring-context依赖(包含了其他依赖,core,beans,aop,expression,spring-jcl),spring使用了jcl日志门面,spring-jcl是spring的桥接器(桥接器是干啥的 忘了)

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.2.6.RELEASE</version>
</dependency>

创建一个普通类,有一个普通方法

public class User {
    public void sayHello() {
        System.out.println("hello...");
    }
}

创建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">

    <!--使用配置文件 创建对象-->
    <bean id="user" class="org.example.beans.User"></bean>
</beans>

获取对象:使用spring的context加载配置文件,并从context中获取这个对象

public class MainHello {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("user.xml");

        User user = context.getBean("user", User.class);
        System.out.println("user = " + user);
        user.sayHello();
    }
}

输出:

user = org.example.beans.User@367ffa75
hello...

IOC容器

概念和原理

IOC(Inversion of Control):控制反转,把对象创建和对象直接的调用过程交给Spring进行管理;使用IOC的目的是为了降低耦合度;入门案例就是IOC的实现
IOC底层原理:xml解析、工厂模式、反射,在a类中调用b类对象的方法时,a类通过调用工厂类的方法获得b类对象,进而调用方法,在工厂类中,通过xml解析获取到类的class,通过反射创建b类对象,降低了a类和b类的耦合,当b类的类路径改变时,工厂类一样可以正常返回b类对象

IOC的BeanFactory接口

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

Spring提供IOC容器的两种实现方式(两个接口)

  • BeanFactory,IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用,加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
  • ApplicationContext,BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用,加载配置文件时候就会把在配置文件对象进行创建

ApplicationContext有接口实现类

  • FileSystemXmlApplicationContext
  • ClassPathXmlApplicationContext

IOC操作: Bean管理(创建对象,注入属性)

Bean管理:指两个操作,Spring创建对象Spring注入属性
Bean管理的两种方式:基于xml配置文件方式实现基于注解方式实现

Bean的创建和属性注入:(xml方式)

  • 创建对象:使用bean标签(注意有两种:普通bean和工厂bean)
  • 注入属性:
    • 使用构造器或者set方法注入,直接写属性注入或者使用p名称空间(需要引入p名称空间)
    • 注入null和特殊符号
    • 注入内部bean:创建bean时,bean的某个属性是一个对象,直接创建为一个bean
    • 注入外部bean:使用ref标签
    • 级联赋值:注入外部bean,使用ref标签,可以直接设置ref对象的属性的值(需要有该对象的get方法)
    • 注入集合,数组、List、Map、Set(可以使用util标签单独创建,被其他多个bean使用,需要引入util名称空间)

Bean的类型:Spring有两种bean,一种普通bean,另外一种工厂bean(实现接口FactoryBean)

  • 普通bean:在配置文件中定义bean类型就是返回类型
  • 工厂bean:在配置文件定义bean类型可以和返回类型不一样,实现FactoryBean接口,重写方法,定义返回类型,这里一般使用工厂模式加反射返回指定类型

Bean的作用域:

  • 在Spring里面,设置创建bean实例是单实例还是多实例,默认情况下,bean是单实例对象
  • 创建对象时使用scope标签配置,singleton,prototype,request,session
  • singleton,prototype区别:1,一个单例,一个多例,2,单例在加载配置文件时就已经创建好对象了,多例是在getBean时创建,每次创建都是一个新的对象
  • request,session:会将对象放在对应域对象中
<?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"
       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">

    <!--基于xml方式 创建对象
        id: 唯一标识 不能重复
        class: 类的全限定名
        创建对象时使用无参构造方法
    -->
    <bean id="user" class="org.example.beans.User"></bean>

    <!--基于xml方式 注入属性(先创建对象 再注入属性)
        使用DI: 依赖注入(注入属性) 是IOC的一种实现
        使用有参构造或者set方法
    -->
    <bean id="person" class="org.example.beans.Person">
        <!--使用set方法注入 类必须有对应属性的set方法-->
        <property name="name" value="zhangsan"></property>
        <property name="age" value="10"/>
        <property name="male" value="0"/><!--0和1可以表示false和true-->
    </bean>
    <!--使用构造方法注入-->
    <bean id="student" class="org.example.beans.Student">
        <constructor-arg name="name" value="lisi"></constructor-arg>
        <constructor-arg index="1" value="12"></constructor-arg>
    </bean>
    <!--使用p名称空间(set方法注入的简化)-->
    <bean id="personP" class="org.example.beans.Person" p:name="lisiP" p:age="10" p:male="true">
    </bean>

    <!--注入null和特殊符号-->
    <bean id="personNull" class="org.example.beans.Person">
<!--        <property name="name"><null/></property>-->
        <property name="name">
            <value><![CDATA[<<xxx]]></value><!--或者使用&lt;&gt;转义的<>-->
        </property>
    </bean>

    <!--注入其他bean 外部bean-->
    <bean id="userServiceImpl" class="org.example.service.UserServiceImpl">
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>
    <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"></bean>

    <!--级联赋值1 - 注入其他bean 内部bean 同时给其他bean的字段赋值-->
    <bean id="employee" class="org.example.beans.Employee">
        <property name="name" value="zhansan"></property>
        <property name="department"><!--注入employee的时候直接new一个Department-->
            <bean id="department" class="org.example.beans.Department">
                <property name="name" value="财务"></property>
            </bean>
        </property>
    </bean>
    <!--级联赋值2 - 注入其他bean 内部bean 同时给其他bean的字段赋值-->
    <bean id="employee1" class="org.example.beans.Employee">
        <property name="name" value="lisi"></property>
        <property name="department" ref="department"></property>
        <property name="department.name" value="保安"></property><!--需要employee类有该属性的get方法-->
    </bean>
    <bean id="department" class="org.example.beans.Department"></bean>

    <!--注入集合属性-->
    <bean id="student1" class="org.example.beans.Student">
        <property name="array">
            <array>
                <value>s1</value>
                <value>s2</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>v1</value>
                <value>v2</value>
            </list>
        </property>
        <property name="courseList">
            <list>
                <ref bean="course1"></ref>
                <ref bean="course2"></ref>
            </list>
        </property>
        <property name="map">
            <map>
                <entry key="k1" value="v1"/>
                <entry key="k2" value="v2"/>
            </map>
        </property>
    </bean>
    <bean id="course1" class="org.example.beans.Course">
        <property name="name" value="Chinese"></property>
    </bean>
    <bean id="course2" class="org.example.beans.Course">
        <property name="name" value="English"></property>
    </bean>

    <!--提取公共部分 使用util空间-->
    <util:list id="commonStringList">
        <value>abc</value>
        <value>abc</value>
    </util:list>
    <bean id="student2" class="org.example.beans.Student">
        <property name="list" ref="commonStringList"></property><!--引用公共list bean-->
    </bean>

    <!--创建工厂bean 该bean实际返回一个Course-->
    <bean id="myFactoryBean" class="org.example.beans.MyFactoryBean"/>

    <!--bean作用域 scope-->
    <bean id="studentScope1" class="org.example.beans.Student" scope="singleton"/>
    <bean id="studentScope2" class="org.example.beans.Student" scope="prototype"/>

</beans>

Bean的生命周期:(7个步骤)

  • 调用构造器创建bean实例
  • 调用set方法设置bean的属性值和其他bean引用
  • 调用后置处理器的postProcessBeforeInitialization()方法
  • 调用初始化方法(init-method标签指定)
  • 调用后置处理器的postProcessAfterInitialization()方法
  • 获取到bean实例进行使用
  • 调用销毁方法(destroy-method标签指定),当ioc容器关闭时调用

注意:后置处理器是全局的,所有bean的创建都会调用,需要向ioc注入BeanPostProcessor接口的实现类对象

<?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"
       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生命周期-->
    <bean id="beanLife" class="org.example.beans.BeanLife" init-method="initBean" destroy-method="destroyBean">
        <constructor-arg name="name" value="first"></constructor-arg>
        <property name="otherField" value="set property"></property>
    </bean>
    <bean id="postProcessor" class="org.example.config.MyPostProcessor"/>

</beans>

Bean的自动装配:(xml方式)

  • 使用autowire标签指定装配规则(名称或者类型),选择byNamebyType
  • 按名称,必须属性名称与id相同;按类型必须该类型的只有一个bean或者多个但其中一个primary="true"
<?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"
       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生命周期-->
    <bean id="employeeAutowire" class="org.example.beans.Employee" autowire="byName">
        <property name="name" value="one"/>
    </bean>

    <!--by name-->
    <bean id="department" class="org.example.beans.Department" primary="false">
        <property name="name" value="first"/>
    </bean>

    <!--by type and is primary -->
    <bean id="departmentAutowire" class="org.example.beans.Department" primary="true">
        <property name="name" value="second"/>
    </bean>

</beans>

xml中引入外部properties配置文件:

  • 需要引入context名称空间
  • 使用context:property-placeholder引入配置文件
<?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"
       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">

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

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

Bean的创建和属性注入:(注解方式)

  • 使用注解的目的:简化xml配置
  • Spring对创建对象提供4个注解:@Component,@Service,@Controller,@Repository,功能一样
    • 分别代表:普通组件,业务层组件,控制层组件,持久层组件
  • 需要开启注解扫描:<context:component-scan base-package="org.example"></context:component-scan>,默认扫描所有注解,多个包使用逗号隔开,也可以使用include或exclude过滤
  • 属性注入:
    • @Autowired:先根据类型自动装配,如果存在多个同类型的则使用属性名称匹配
    • @Qualifier:跟@Autowired一起使用,指定名称进行注入
    • @Resource:可以根据类型注入,也可以根据名称注入,默认先根据名称匹配,如果没有找到,再根据类型匹配,这个是javax扩展包中的注解,需要导入依赖
    • @Value:注入普通类型属性,可以使用${a.b:c}取变量a.b的值,默认值为c,或者使用#{}里面写表达式

完全注解开发,使用配置类替代xml配置文件中的组件扫描配置:

@Configuration //作为配置类 代替xml配置文件中的配置
@ComponentScan(basePackages = {"org.example.annotation"})
public class SpringConfig {
}

启动方式修改为new AnnotationConfigApplicationContext(SpringConfig.class)

总结

  • 创建对象:
    • 使用xml配置文件
    • 使用注解:@Component,@Service,@Controller,@Repository
  • 注入属性:
    • 使用xml配置文件
    • 使用注解: @Autowired,@Qualifier,@Resource,@Value(可以使用#{},${})
  • bean的类型:工厂bean,普通bean
  • Bean的作用域:scope标签,singleton,prototype,request,session
  • bean生命周期:BeanPostProcessor接口注入后置处理器(在初始化方法的前后执行),每个bean可以设置初始化方法(bean初始化后执行,也即是执行完构造方法和一些属性set后)和销毁方法(容器关闭时执行)
    • 执行顺序:bean构造器,bean其他注入属性的方法,后置处理器before方法,bean自己指定的init方法,后置处理器的post方法,容器关闭时bean自己指定的销毁方法
    • 如果使用完全注解开发,初始化方法和销毁方法在@Bean注解中可以指定
  • IOC容器基本实现:BeanFactory,子接口:ApplicationContext,几个实现:
    • FileSystemXmlApplicationContext
    • ClassPathXmlApplicationContext
    • AnnotationConfigApplicationContext

AOP

概念

  • 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  • 术语:
    • 连接点,类里面可以被增强的方法称为连接点
    • 切入点,实际被增强的方法称为切入点
    • 通知(增强),实际增强的逻辑部分称为通知,前置通知,后置通知,环绕通知,异常通知,最终通知
    • 切面,将通知应用到切入点的过程称为切面

AOP底层使用动态代理,有接口情况,使用JDK动态代理,创建接口实现类代理对象,增强类的方法;没有接口情况,使用CGLIB(Code Generation Library)动态代理,创建子类的代理对象,增强类的方法。

JDK动态代理主要是使用Proxy.newInstance()创建代理类对象,每次调用代理类的对象的方法时,都会调用参数中InvocationHandler的invoke()方法,该方法的参数中包含被代理对象、调用的方法、方法的参数。

AOP操作(准备工作)

  • Spring框架一般是基于AspectJ实现AOP操作,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。
    引入依赖:
   <!--aop依赖-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib -->
        <dependency>
            <groupId>net.sourceforge.cglib</groupId>
            <artifactId>com.springsource.net.sf.cglib</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance -->
        <dependency>
            <groupId>org.aopalliance</groupId>
            <artifactId>com.springsource.org.aopalliance</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/com.springsource.org.aspectj.weaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>com.springsource.org.aspectj.weaver</artifactId>
            <version>1.6.8.RELEASE</version>
        </dependency>

基于AspectJ实现AOP操作:xml配置文件方式、注解方式

切入点表达式:

  • 作用:知道对哪个类里面的哪个方法进行增强
  • 语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]))
举例1:对com.atguigu.dao.BookDao类里面的add进行增强
execution(* com.atguigu.dao.BookDao.add(..))
举例2:对com.atguigu.dao.BookDao类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
举例3:对com.atguigu.dao包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))

使用AOP

  • 对于任意类的任意方法,都可以配置一个增强类,对被增强的类的方法进行增强(前置通知@Before、后置通知@After、环绕通知@Around、异常通知@AfterThrowing、返回通知@AfterReturning
  • 通常使用注解方式开发,增强类使用@Aspect,启动类开启自动代理@EnableAspectJAutoProxy,增强类和被增强类都要放入ioc容器中
  • 使用配置文件配置aop的话,不用配置开启自动代理

使用注解:

@Aspect //该类为增强类
@Order(1) //配置增强的优先级 越小优先级越高
@Component
public class UserDaoImplAop {
    /**
     * 抽取切入点 后面的切入点就可以写这个抽取方法的方法名称()
     */
    @Pointcut("execution(* org.example.dao.UserDaoImpl.updateUser(..))")
    public void pointDemo() {

    }

    //前置通知
    @Before(value = "pointDemo()")
    public void before() {
        System.out.println("before...");
    }

    //@After表示最终通知 方法有没有异常都会执行 方法执行完但在return前执行
    @After(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))")
    public void after() {
        System.out.println("after...");
    }

    //后置通知 增强方法return后执行
    @AfterReturning(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning...");
    }

    //异常通知 方法出现异常后执行
    @AfterThrowing(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }

    //环绕通知 在方法执行前后执行
    @Around(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around before..");
        proceedingJoinPoint.proceed();
        System.out.println("around after..");
    }
}

使用配置文件:

<?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"
       xmlns:util="http://www.springframework.org/schema/util"
       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.xsd
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--测试使用配置文件配置切面-->
    <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"/>
    <bean id="userDaoImplAop" class="org.example.aop.UserDaoImplProxy2"/>

    <aop:config>
        <!--这个相当于抽取的切入点-->
        <aop:pointcut id="pointDemo" expression="execution(* org.example.dao.UserDaoImpl.updateUser(..))"/>

        <!--配置切面 使用哪个增强类-->
        <aop:aspect ref="userDaoImplAop">
            <!--配置增强到具体的方法上-->
            <aop:before method="before" pointcut-ref="pointDemo"></aop:before>
            <!--这里的切入点没有抽取-->
            <aop:after method="after" pointcut="execution(* org.example.dao.UserDaoImpl.updateUser(..))"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

总结

  • 启动类开启自动代理@EnableAspectJAutoProxy
  • 切入点表达式的写法
  • 增强类上使用注解@Aspect,增强方法上使用注解(@Before@After@Around@AfterThrowing@AfterReturning
  • 增强类和被增强类都要放入ioc容器中
  • 目的是在被执行的方法执行前后、返回前后、异常时增强一些逻辑

使用Spring JdbcTemplate操作数据库(增删改查)

Spring框架对jdbc进行的封装,使用JdbcTemplate方便实现对数据库的操作。

需要引入的数据库操作相关依赖:

        <!--整合其他mybatis等需要的依赖
                包含了spring-jdbc spring-tx spring-beans spring-core
            -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
        </dependency>

        <!--mysql jdbc-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

使用:在创建JdbcTemplate时,传入一个DataSource对象即可,比如druid的连接池,然后就可以JdbcTemplate进行数据库操作了

@Component
public class UserDaoImp implements UserDao {

//    @Autowired
    @Resource
    private JdbcTemplate jdbcTemplate;

    //插入数据
    @Override
    public void add(User user) {
        String sql = "insert into book values (?,?,?)";
        Object[] args = {user.getId(), user.getName(), user.getStatus()};
        int add = jdbcTemplate.update(sql, args);
        System.out.println("add = " + add);
    }

    //更新数据
    @Override
    public void update(User user) {
        String sql = "update book set name=?, status=? where id=?";
        Object[] args = {user.getName(), user.getStatus(), user.getId()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println("update = " + update);
    }

    //删除数据
    @Override
    public void deleteById(String id) {
        String sql = "delete from book where id=?";
        int delete = jdbcTemplate.update(sql, id);
        System.out.println("delete = " + delete);
    }

    //查询总数
    @Override
    public long count() {
        String sql = "select count(*) from book";
        Long aLong = jdbcTemplate.queryForObject(sql, Long.class);
        System.out.println("aLong = " + aLong);
        return aLong;
    }

    //查询对象
    //BeanPropertyRowMapper 这个默认mapper是怎么匹配bean字段和表字段的?
    @Override
    public User selectById(String id) {
        String sql = "select * from book where id=?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
        System.out.println("user = " + user);
        return user;
    }

    //查询多个对象
    @Override
    public List<User> selectAll() {
        String sql = "select * from book";
        List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        System.out.println("userList = " + userList);
        return userList;
    }

    //批量插入与单条插入类似 只是多次执行单条语句
    @Override
    public int[] batchAdd(List<User> userList) {
        String sql = "insert into book values (?,?,?)";

        List<Object[]> args = userList.stream().map(x -> new Object[]{x.getId(), x.getName(), x.getStatus()})
                .collect(Collectors.toList());
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
        return ints;
    }

    //批量更新 与单条更新类似 只是多次执行单条语句
    @Override
    public int[] batchUpdate(List<User> userList) {
        String sql = "update book set name=?, status=? where id=?";

        List<Object[]> args = userList.stream().map(x -> new Object[]{x.getName(), x.getStatus(), x.getId()})
                .collect(Collectors.toList());
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
        return ints;
    }

    //批量删除 与单条删除类似 只是多次执行单条语句
    @Override
    public int[] batchDelete(List<String> idList) {
        String sql = "delete from book where id=?";
        List<Object[]> args = idList.stream().map(x -> new Object[]{x})
                .collect(Collectors.toList());
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints));
        return ints;
    }
}

Spring事务管理

事务概念

  • 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
  • 四个特性(ACID):(1)原子性(2)一致性(3)隔离性(4)持久性
  • 事务的隔离级别:读未提交、读已提交、可重复读、串行化。后面三个分别解决了脏读、不可重复读、幻读的问题。MySQL默认是可重复级别
    • 脏读:未提交的事务a读取了另一个未提交事务b插入的数据,b事务回滚了,导致a事务读到了脏数据,是一个很严重的问题
    • 不可重复读:未提交的事务a读取到了另一个已提交事务b修改的数据(在b提交前读到了),a事务在b事务提交前后读取到的数据不一样,是一个现象
    • 幻读:未提交的事务a读取到了另一个已提交事务b插入的数据(在b提交前没读到),a事务在b事务提交后读到了
隔离级别、解决的问题 脏读 不可重复读 幻读
读未提交 Read Uncommitted n n n
读已提交 Read Committed y n n
可重复读 Read Repeatly y y n
串行化 Serialization y y y

Spring事务操作

  • Spring事务管理是基于AOP实现的
  • 事务一般添加到JavaEE三层结构里面Service层(业务逻辑层)
  • 管理事务的两种方式:编程式事务管理和声明式事务管理(一般使用声明式)
    • 声明式事务管理:(1)基于注解方式(2)基于xml配置文件方式。
  • Spring事务管理API,提供一个接口PlatformTransactionManager,代表事务管理器,针对不同的框架提供不同的实现类(如Jpa,Hibernate,使用jdbc则用DataSourceTransactionManager)

声明式事务管理:(使用注解配置)
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"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/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
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解扫描
        使用注解
        -->
    <context:component-scan base-package="org.example"></context:component-scan>

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

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--使用构造器和set注入都可以 也有set方法-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--
        测试事务
    -->
    <!--配置事务管理器 这里配置为jdbc的事务管理器DataSourceTransactionManager 它的顶级接口有其他各个框架的事务管理器实现-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--使用构造器和set注入都可以 也有set方法-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置注解驱动的事务管理器
    -->
<!--    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>-->
</beans>

使用注解@Transactional,可以加在类上面,也可以加在方法上面

    //测试事务
    //如果方法内发生异常 所有操作都会回滚
    //如果放在类上 表示所以方法都是事务方法
    @Transactional(propagation = Propagation.REQUIRED, //有7种,常用的有两种REQUIRED和REQUIRED_NEW,考虑a方面调用b方法,对于前者,如果a有事务,调用b后使用a里面的事务,如果a没有事务,创建新事务;对于后者,不管a有没有事务都创建新事务
            isolation = Isolation.REPEATABLE_READ, //事务隔离级别,有4种,对应三个读问题(脏读,不可重复读,幻读)
            timeout = -1, //超时时间,规定事务提交时间,未提交则回滚
            readOnly = false, //是否只读,默认false,可以读可以写,如果只读则事务内只读数据
            rollbackFor = {}, //回滚,设置出现哪些异常进行回滚
            rollbackForClassName = {},
            noRollbackFor = {},//不回滚,设置出现那里写异常不回滚
            noRollbackForClassName = {})
    public void testTx(String id1, String id2) {
        userDao.deleteById(id1);
        int i = 1 / 0;
        userDao.deleteById(id2);
    }

声明式事务管理:(使用注解xml配置)
aop的expression需要依赖包:

        <!--xml方式配置事务需要aop的这个依赖 使用注解就不需要(aop中的expression需要依赖)
        Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.reflect
        -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>com.springsource.org.aspectj.weaver</artifactId>
        </dependency>

配置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:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/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
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启注解扫描
        使用注解
        -->
    <!--    <context:component-scan base-package="org.example"></context:component-scan>-->
    <!--不与完全注解开发的包内bean重复-->
    <context:component-scan base-package="org.example.dao"></context:component-scan>
    <context:component-scan base-package="org.example.service"></context:component-scan>

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

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--使用构造器和set注入都可以 也有set方法-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--
        测试事务
        使用配置文件配置事务
    -->
    <!--1 配置事务管理器 这里配置为jdbc的事务管理器DataSourceTransactionManager 它的顶级接口有其他各个框架的事务管理器实现-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--使用构造器和set注入都可以 也有set方法-->
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置使用什么事务管理器 如果用xml方式配置事务 这里就不需要配置了-->
<!--    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>-->

    <!--2 配置事务通知(增强部分)-->
    <tx:advice id="myTxAdvice">
        <!--配置事务的参数-->
        <tx:attributes>
            <!--准确配置哪个方法上面添加事务(只是配置名字)-->
            <tx:method name="testTx2" isolation="REPEATABLE_READ" propagation="REQUIRED"></tx:method><!--其他属性配置-->
            <!--也可以使用模糊匹配的方式配置-->
<!--            <tx:method name="testTx*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--3 配置切入点和切面(配置哪个地方需要增强 切面是指增强的过程 也即是将通知配置到切入点的过程)-->
    <aop:config>
        <!--配置切入点 可以将切入点抽出来作为一个ref进行配置
            expression这个需要com.springsource.org.aspectj.weaver依赖!!!
        -->
        <aop:pointcut id="pointRef" expression="execution(* org.example.service.UserService.testTx2(..)))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="myTxAdvice" pointcut-ref="pointRef"></aop:advisor>

        <!--配置切面 这里直接写了切入点-->
<!--        <aop:advisor advice-ref="myTxAdvice" pointcut="execution(* org.example.service.UserService.testTx(..)))"></aop:advisor>-->
    </aop:config>
</beans>

事务操作(完全使用注解配置,将上面注解实现时xml里面的内容放在配置类里面)

@Configuration //配置类
@ComponentScan(basePackages = {"org.example"}) //包扫描
@EnableTransactionManagement(mode = AdviceMode.PROXY) //开启事务管理 Proxy是代理模式(实现有jdk和cglib) 还有一个aspectj模式(?)
public class TxConfig {

    //注入dataSource
    @Bean //方法名为bean的名称
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/user_db_spring");
        dataSource.setUsername("root");
        dataSource.setPassword("999999");
        return dataSource;
    }

    //注入JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate(@Autowired @Qualifier("dataSource") DataSource dataSource) { //autowired自动注入
        return new JdbcTemplate(dataSource);
    }

    //注入事务管理器transactionManager
    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

总结

  • 开启事务管理,注入事务管理对象,使用@Transactional注解

Spring5新特性

  • 整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
  • Spring5框架自带了通用的日志封装,移除Log4jConfigListener,官方建议使用Log4j2
    • 日志配置文件:log4j2.xml
    • 使用sl4j日志门面,需要使用log4j2的适配器
    • private static final Logger log = LoggerFactory.getLogger(MainApplication.class);
<!--log4j2适配slf4j的适配器(log4j的日志打到slf4j里面) 这里是log4j的2.17.1版本log4j2
            包含了log4j-api和log4j-core slf4j-api
            = slf4j + log4j2
            -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-slf4j-impl</artifactId>
                <version>2.17.1</version>
            </dependency>
  • Spring5框架核心容器支持@Nullable@NonNull注解,可以使用在方法、属性、参数上面,表示可以为空、不为空(只是标注,并不影响编译和运行)
  • Spring5核心容器支持函数式风格GenericApplicationContext,将对象注入context
@Test
    public void testGenericApplicationContext() {
        //1 创建GenericApplicationContext对象
        GenericApplicationContext context = new GenericApplicationContext();

        //2 调用context的方法对象注册
        context.refresh();
        context.registerBean(User.class, () -> new User("1", "a", "1")); //没有指定beanName那么就是类的全限定名名称
        context.registerBean(User.class, () -> new User("2", "a", "1")); //覆盖了上面的bean
        context.registerBean("user",User.class, () -> new User("3", "a", "1")); //新注册的

        //3 获取在spring注册的对象
        String[] names = context.getBeanNamesForType(User.class);
        System.out.println("names = " + Arrays.toString(names)); //names = [org.example.beans.User, user]

        int count = context.getBeanDefinitionCount(); //获取bean的数量
        System.out.println("count = " + count); //2

        //get bean
        User user = (User)context.getBean("org.example.beans.User");
        System.out.println("user = " + user); //user = User(id=2, name=a, status=1)

        User user1 = context.getBean("user", User.class);
        System.out.println("user1 = " + user1); //user1 = User(id=3, name=a, status=1)

        System.out.println("user == user1 = " + (user == user1)); //false
    }
  • Spring5支持整合JUnit5
    使用junit4:需要引入junit4依赖
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
<!--                <scope>test</scope>-->
            </dependency>
@RunWith(SpringJUnit4ClassRunner.class) //spring使用junit4框架测试
@ContextConfiguration(value = "classpath:tx.xml") //代替了使用xml文件获取context
//@ContextConfiguration(classes = TxConfig.class) //代替了使用配置类获取context
public class JUnit4Test {

    @Autowired
    UserService userService; //直接注入

    @Test
    public void testTx() {
        userService.truncateTable("book");

        userService.addUser(new User("1001", "a", "a"));
        userService.addUser(new User("1002", "b", "b"));

        userService.testTx("1001", "1002");
    }
}

使用JUnit5:需要引入junit5的依赖

            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
                <version>5.9.0-M1</version>
                <!--在src下也使用测试 去掉test scope-->
<!--                <scope>test</scope>-->
            </dependency>
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration(value = "classpath:tx.xml") //代替了使用xml文件获取context
//@ContextConfiguration(classes = TxConfig.class) //代替了使用配置类获取context

@SpringJUnitConfig(locations = "classpath:tx.xml") //复合注解 相当于上面两个
//@SpringJUnitConfig(classes = TxConfig.class) //复合注解 相当于上面两个
public class JUnit5Test {

    @Autowired
    UserService userService; //直接注入

    @Test //使用junit5
    void testTx() { //可以不写public
        userService.truncateTable("book");

        userService.addUser(new User("1001", "a", "a"));
        userService.addUser(new User("1002", "b", "b"));

        userService.testTx("1001", "1002");
    }
}

Spring5框架新功能(Webflux)

需要基础知识:SpringMVC,SpringBoot,Maven,Java8新特性

posted on 2021-01-04 20:30  Bingmous  阅读(57)  评论(0编辑  收藏  举报