Spring笔记(Spring5尚硅谷2020版)

Spring

1.1 简介

full-stack轻量级开源J2EE框架,以IOC和AOP为内核

1.2 Spring开发步骤(quick start)

  1. 在maven中导入spring坐标。核心依赖有core、beans、context、expression。而引入mvc不仅包含这四个还附赠aop和mvc即web相关依赖,何乐不为?且要引入junit,四版本,为了测试(@Test用于方法上);日志也顺便引入了

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
    </dependencies>
    
  2. 在bean.xml中创建bean(dao、service,接口和实现等)

    <bean id="user" class="ind.deng.spring5.User"/>
    
  3. 创建applicationContext.xml

  4. 在配置文件中配置

  5. 创建ApplicationContext对象getBean

因为初始只做个简单测试即可,345步直接跳过,用Test方法测试即可:

public class TestUser {
    @Test
    public void testAdd(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件
        User user = context.getBean("user", User.class);//获取容器中的对象
        System.out.println(user);
        user.add();
    }
}

2.1 IOC(控制反转)

是为了降低耦合度的

对象由spring创建,管理,装配;或者说对象之间的创建和调用由spring管理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9cswCMSn-1639306842613)(SSM.assets/image-20211002235455866.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8iLn90R-1639306842614)(SSM.assets/image-20211003000405995.png)]

2.2 注解

注:先学xml配置文件方式再学注解是比较合理的方式

要是用注解要引入spring-aop依赖,而我们之前已经一劳永逸了

2.2.1 bean的创建

@Component:说明此类已被注册到Spring容器中

dao层等价的是@Repository

service层等价的是@Service

控制器层为@Controller

上面四个组件功能一致,用来将bean注入到容器中,用的地方不一样,尽量别乱用

使用注解要在xml配置文件中引入context命名空间并加上扫包的步骤:

<context:component-scan base-package="包路径"></context:component-scan>

要扫描多个包,路径可以用“,”隔开,或者扫描上层包

比如,Controller层的注解:

当只有value属性时,可以直接写@Controller(“test01”)

也可以直接写@Controller,此时bean的id为类的名称首字母小写:myController

@Controller(value="test01")
public class MyController {
    public void test01(){
        System.out.println("controller...");
    }
}

包扫描可以更加细致更加定制化一些

<context:component-scan base-package="ind.deng.spring5" use-default-filters="false">
   <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

use-default-filters默认为true,使用spring默认的filter。设置为false后可以自定义filter(只扫描给定的组件)。上例是过滤ind.deng.spring5包中所有@Controller注解。能看懂即可

<context:component-scan base-package="ind.deng.spring5.controller">
   <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

和上面的例子相反,没有use-default-filters=“false”,扫描所有组件,除了<context:exclude-filter type="" expression=""/>指定的内容

2.2.2 bean的属性注入

@Autowired自动装配(byType)

@Qualifier (byName)

@Resource 先byName,找不到再byType。

上面三个是对象属性注入。小细节:前两个是spring自带的注解,第三个是javax中的扩展注解,所以spring更建议使用前两个

例子:

@Service
public class UserService {
    //不需要set方法了
    //没有写value值则按类型注入;
    //写value值,如果UserDaoImpl实现类@Repository注解没写value,那这里的value值就是userDaoImpl
    //否则两者必须匹配才能按名称注入
    @Resource
    private UserDao userDao;

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

@Value(“”)注入属性值 常结合SpEL(spring表示式语言,el的升级版)注入配置文件中的值

2.2.3 完全注解开发

以上注解不能全部替代xml,比如非自定义的bean(第三方的类)、加载配置文件、组件扫描、import等。有一些新的注解

@Configuration:说明此类是配置类(也已经被注册到Spring容器中),就像xml配置文件。类中有方法。方法前@Bean对应bean标签,方法名相当于id属性,返回值相当于class属性

@ComponentScan用于包扫描

Test中用AnnotationConfigApplicationContext获取Spring容器

@PostConstruct与@PreDestroy等同于init和destroy

还有其他杂七杂八的,有的配起来反而比xml麻烦。这种纯注解其实就是springboot采用的方式。在这里还是xml和注解结合最方便

注解能解析el表达式,但普通的API不行。常用的注解也就十来个。springboot中比较多

2.4 底层原理

xml解析、工厂模式、反射(得到类的字节码文件以操作类的所有内容,Class.forName())

工厂模式只能实现部分解耦,如工厂生产dao实例(return new),service用工厂类的静态方法获取dao对象,降低了dao和service的耦合度,但工厂和dao又耦合在一起了.

而用了以上三种方法后,耦合度可以进一步降低

public class UserFactory {
    public static User getUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        String classValue= "通过xml解析获取bean中class的值,即那个全类名";
        Class clazz = Class.forName(classValue);
        return (User) clazz.newInstance();
    }
}

因为即使bean改变了,工厂仍能正确获取该对象。注意上面没有写出xml解析的细节,且newInstance()方法已经过时。

知道这是spring底层原理就足够了。底层是什么?是对象工厂

2.5 两种实现方式(两个接口)

BeanFactory:IOC容器的基本实现,设计spring时使用的,不是给开发人员用的(也能用);加载配置文件时不创建对象,而是使用时才创建。

ApplicationContext:是前者的子接口,功能更强大,开发使用这个;加载配置文件时就会创建。因为一般都是web项目,在服务器启动时就加载好更省事,访问时就快了

2.6 ApplicationContext的两个常用实现类

ClassPathXmlApplicationContext参数是类路径src下(或者resources下)擦皮鞋方便记忆

FileSystemXmlApplicationContext参数是绝对路径(盘符之类打的)

2.7 Bean管理

指spring创建对象和注入属性。创建对象时默认执行无参构造方法,没有则报错

2.7.1 Bean标签中的属性
id

找到注入的对象的唯一标识符

name

早期属性,和id功能差不多,但name中可以有些特殊符号,较少使用

class

值为全类名,将指定类的对象注入到容器中

scope(作用域)

常用singleton(单例,只创建一个对象,多次使用;xml被加载时,实例化bean)和prototype(多例,每次新建一个对象;getBean时,实例化bean)。默认单例。还有不太常用的request和session,就是把创建的对象放在这两个域中。

init-method和destroy-method

init-method=“方法名”;

destroy-method=“方法名”;

2.7.2 Bean实例化三种方式

  1. 无参构造方法:bean中指定id和class
  2. 工厂静态方法:创建工厂类及其中的静态方法返回需要的对象。xml中class为工厂类,后面跟着factory-method属性,值为静态方法
  3. 工厂实例方法:方法不是static,所以先实例化工厂对象再使用方法,用两个bean(麻烦)。factory-bean、factory-method

2.8 依赖注入(DI)

是IOC的一种具体实现。就是注入属性。IOC实现解耦,但代码间仍有依赖关系,如service需要用到dao。而这种依赖交给spring管理,把dao注给service,我们直接调service就行了(外部bean注入)

依赖注入三种方式:
  1. setXxx注入:

    <property name="" value=""></property>
    
  2. 有参构造函数注入:

    <constructor-arg name="" value=""></constructor-arg>
    

    也可以把name换成index以按照类中属性的顺序注入,但很少用

  3. p命名空间注入。与法一大同小异,更简便些

    <bean id="user" class="ind.deng.spring5.User" p:name="邓玉琪"></bean>
    

类中必须有无参构造,才能在spring中定义bean

将某个属性设成空值:

<bean id="user" class="ind.deng.spring5.User">
    <property name="name">
        <null/>
    </property>
</bean>

如果属性值有特殊字符,如<<>>和标签冲突,可以使用转义 &gt &lt等

也可以使用CDATA

<bean id="user" class="ind.deng.spring5.User">
    <property name="name">
        <value>
            <!--CDATA[]内是要输出的值-->
            <![CDATA[<<邓玉琪>>]]>
        </value>
    </property>
</bean>
引入其他配置文件

可以将配置文件按模块拆分为多个,统一引入主配置文件使用

<import resource="xxx.xml"/>

自动装配:约定优于配置。只适用于rel类型

 autowire=“byName|byType|constructor”

IOC容器中有一个bean的id值恰好为该bean中的ref属性值,则自动装配.前提:只有一个bean满足此要求,不然无法辨别

constructor类似byType,但支持多个满足要求(和constructor参数一致的)的bean

自动装配减少代码量但降低可读性

2.9 外部bean

用ref属性,在userService中注入userDao属性,这样前者就可以使用后者的方法,再做些其他操作。

<bean id="userDaoImpl" class="ind.deng.spring5.dao.UserDaoImpl"></bean>
<bean id="userService" class="ind.deng.spring5.service.UserService">
    <property name="userDao" ref="userDaoImpl"></property>
</bean>

dao层分为接口和实现类,里面有个add方法,比较简单就不贴出来了。service层类:

public class UserService {
    //将dao对象当做属性注入。既然是属性,要么有set方法要么有无参构造才能注入并给service使用
    //如果只是默认的无参构造的话,就没dao什么事了,那我还要它作为属性干啥?
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void update(){
        System.out.println("update...");
        //把dao当做属性就是拿来用的
        userDao.add();
    }
}

2.10内部bean

用实体类表示关系表之间的关系

比如部门和员工是一对多,可以在员工类里声明部门属性,然后采取以下内部bean方式注入

<bean id="emp" class="ind.deng.spring5.bean.Emp">
    <property name="ename" value="邓玉琪"/>
    <property name="gender" value=""/>
    <property name="dept">
        <!--以bean代替value-->
        <bean id="dept" class="ind.deng.spring5.bean.Dept">
            <property name="dname" value="科研部"/>
        </bean>
    </property>
</bean>

2.11 级联赋值(在bean注入另一个bean)

法一:外部bean。前面用过,只不过这次给属性赋值了

<bean id="emp" class="ind.deng.spring5.bean.Emp">
    <property name="ename" value="邓玉琪"/>
    <property name="gender" value=""/>
    <property name="dept" ref="dept">
    </property>
</bean>
<bean id="dept" class="ind.deng.spring5.bean.Dept">
    <property name="dname" value="科研部"/>
</bean>

法二:

<bean id="emp" class="ind.deng.spring5.bean.Emp">
    <property name="ename" value="邓玉琪"/>
    <property name="gender" value=""/>
    <property name="dept" ref="dept"/>
    <!--emp中的dept必须要有get方法。此处会把dept中原本的dname覆盖掉-->
    <property name="dept.dname" value="炊事班">
    </property>
</bean>
<bean id="dept" class="ind.deng.spring5.bean.Dept">
    <property name="dname" value="科研部"/>
</bean>

2.12 将值注入集合

大同小异。都是property开头,再是集合名称,再是value。map是entry

<bean id="collection" class="ind.deng.spring5.bean.Collections">
    <property name="str">
        <array>
            <value>高数</value>
            <value>英语</value>
        </array>
    </property>
    <property name="list">
        <list>
            <value>c++</value>
            <value>java</value>
        </list>
    </property>
    <property name="maps">
        <map>
            <entry key="数三" value="狗都不考"></entry>
            <entry key="外语" value="英语"></entry>
        </map>
    </property>
    <property name="sets">
        <set>
            <value>不允许</value>
            <value>重复</value>
        </set>
    </property>
</bean>

2.13 将对象注入集合

也就是把上面的value改成现在的ref,然后将已经注册的bean注入到集合中

<bean id="collection" class="ind.deng.spring5.bean.Collections">
    <property name="courses">
        <list>
            <ref bean="course1"/>
            <ref bean="course2"/>
        </list>
    </property>
</bean>
<bean id="course1" class="ind.deng.spring5.bean.Course">
    <property name="cname" value="java"/>
</bean>
<bean id="course2" class="ind.deng.spring5.bean.Course">
    <property name="cname" value="c++"/>
</bean>

2.14 将集合注入部分提取出来

在配置文件中引入util命名空间

<!--公共部分-->
<util:list id="bookList" >
    <value>高数</value>
    <value>英语</value>
</util:list>

 <bean id="book" class="ind.deng.spring5.bean.Book">
     <property name="bookList" ref="bookList"/>
 </bean>

2.15 FactoryBean

spring中有两种bean。一种是我们自己定义的普通bean,bean类型就是返回类型;一种是FactoryBean,bean类型可以和返回类型不一样。

使用工厂bean:用一个类实现FactoryBean接口(需要泛型,表示返回对象类型)

public class MyBean implements FactoryBean<Course> {
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("高数");
        return course;
    }
//上面这个方法比较重要,用来设置返回的对象类型;下面俩方法暂时用不到
    @Override
    public Class<?> getObjectType() {
        return null;
    }

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

2.16 Bean生命周期

  1. 通过构造器创建bean实例(无参构造)
  2. 为bean属性设置值或引用其他bean(调用set方法)
  3. 初始化bean(自己配置)
  4. 使用bean
  5. 容器关闭时,调用bean的销毁方法(自己配置)

配置文件:

<bean id="orders" class="ind.deng.spring5.bean.Orders" init-method="init" destroy-method="destroy">
   <property name="oname" value="奶茶"/>
</bean>

Orders类

public class Orders {
    private String oname;

    public Orders() {
        System.out.println("1.通过构造器创建bean实例");
    }

    public void setOname(String oname) {
        System.out.println("2.为bean属性设置值或引用其他bean(onama='奶茶')");
        this.oname = oname;
    }
    public void init(){
        System.out.println("3.初始化bean");
    }
    public void destroy(){
        System.out.println("5.容器关闭时,调用bean的销毁方法");
    }

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

Test方法(要想销毁bean要调用context的close方法,ClassPathXmlApplicationContext(ApplicationContext的实现类)才有close方法)

@Test
public void testLifeCircle(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    Orders orders = context.getBean("orders", Orders.class);
    System.out.println("4.使用bean");
    System.out.println(orders);
    context.close();
}

控制台输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRLk2iR7-1639306842616)(SSM.assets/image-20211210140038977.png)]

后置处理器

加上后置处理器后,第三步即调用init方法前后多了两个处理步骤,bean生命周期变为7步

配置完后置处理器后,配置文件中所有bean初始化时都会执行后置处理器的两个方法

<bean id="orders" class="ind.deng.spring5.bean.Orders" init-method="init" destroy-method="destroy">
   <property name="oname" value="奶茶"/>
</bean>
<bean id="processor" class="ind.deng.spring5.bean.Processor"/>

选择一个类实现BeanPostProcessor接口

public class Processor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("2.5.前置处理器");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3.5.后置处理器");
        return bean;
    }
}

测试方法无变化。控制台输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7t8vx8Rh-1639306842617)(SSM.assets/image-20211210141528576.png)]

2.17 自动装配

根据不同方式,根据属性名称(byName)或属性类型(byType)自动注入

byName:Dept的bean的id值必须与Emp类中的属性值(dept)同名。

<bean id="emp" class="ind.deng.spring5.bean.Emp" autowire="byName"/>
<bean id="dept" class="ind.deng.spring5.bean.Dept"/>

byType代码就不贴了。如果有多个class一样的bean则autowire="byType"则会报错,而用byName就不会有这种顾虑,因此常用byName,匹配也更精准。实际使用时常用注解,省事方便

3.1 AOP(面向切面编程)

不改变源代码而在主干功能中添加新功能添加新功能。可以将业务逻辑的各个部分隔离,使它们间的耦合度降低,提高代码重用

底层基于动态代理

有接口要实现增强,用JDK动态代理,创建接口实现类的代理对象增强方法;

无接口要实现增强,用CGLIB动态代理,创建子类的代理对象增强方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fftDqoDA-1639306842618)(SSM.assets/image-20211102102929427.png)]

3.2 JDK实现动态代理

使用Proxy类里的静态方法newProxyInstance()创建代理对象

方法中有三个参数:

  1. 类加载器ClassLoader
  2. 增强方法所在类实现的接口,支持多个接口。类<?>[] interfaces
  3. InvocationHandler。实现这个接口,创建代理对象,写增强的部分

JDK实现动态代理步骤:

  1. 创建接口,定义方法
  2. 创建接口实现类,实现方法
  3. 使用Proxy类创建接口代理对象
public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        dao.add(1,2);
    }
}
//这是个代理类,通过有参构造获取要代理的对象
public class UserDaoProxy implements InvocationHandler {
    //代理谁的对象,把谁传过来
    private Object obj;
    public UserDaoProxy(Object obj){
        this.obj=obj;
    }
    //增强的逻辑写在invoke中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法之前执行"+method.getName()+",传递的参数"+ Arrays.toString(args));
        Object res = method.invoke(obj,args);

        System.out.println("方法之后执行"+obj);
        return  res;
    }
}

3.3 AspectJ

一些术语:

连接点:类中可以被增强的方法(备胎)。

切入点:真正被增强的方法(正宫)。

通知(增强):实际增强的逻辑部分

通知的类型:前置通知、后置通知、环绕通知、异常通知、最终通知

切面:是一个动作,指把通知应用到切入点的过程

切入点表示式

execution([权限修饰符] 返回值类型.全类名.方法名.(参数列表))

权限修饰符可省略,返回值类型、全类名、方法名都可以用*代替代表任意,参数列表用两个点…表示任意参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p7KNL1ZP-1639306842619)(SSM.assets/image-20211102144611797.png)]

在spring中一般使用AspectJ实现AOP,但AspectJ是独立于AOP的

3.3.1 注解方式实现AOP
  1. 在配置文件中开启组件扫描(配置文件中要引入context和aop命名空间)
<context:component-scan base-package="ind.deng.spring5.aopanno"/>
  1. 用注解创建待增强和增强类的对象
@Component
//待增强的类
public class User {
    public void add(){
        System.out.println("add...");
    }
}
  1. 增强类上面加@Aspect注解
@Component
@Aspect
//增强的类
public class UserProxy {
    public void before(){
        System.out.println("before...");
    }
}
  1. 配置文件中开启生成代理对象
<aop:aspectj-autoproxy/>
  1. 在通知上加上对应注解和切入点表达式
@Component
@Aspect
//增强的类
public class UserProxy {
    //前置通知
    @Before("execution(* ind.deng.spring5.aopanno.User.*(..))")
    public void before(){
        System.out.println("before...");
    }
    //后置通知
    @AfterReturning("execution(* ind.deng.spring5.aopanno.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
    //环绕通知
    @Around("execution(* ind.deng.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前...");
        joinPoint.proceed();
        System.out.println("环绕后...");
    }
    //最终通知
    @After("execution(* ind.deng.spring5.aopanno.User.add(..))")
    public void after(){
        System.out.println("after...");
    }
    //异常通知
    @AfterThrowing("execution(* ind.deng.spring5.aopanno.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }
}

环绕通知和其他通知有点不一样,需要加ProceedingJoinPoint形参,不然其他通知都输出不了。而且 joinPoint.proceed()会抛出异常

控制台输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozXTEiLe-1639306842619)(SSM.assets/image-20211211113634381.png)]

3.3.2 配置文件方式实现AOP

了解即可,注解比较常用

<bean id="user" class="ind.deng.spring5.aopanno.User"/>
<bean id="userProxy" class="ind.deng.spring5.aopanno.UserProxy"/>
<!--配置aop-->
<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* ind.deng.spring5.aopanno.User.add(..))"/>
    <!--切面。ref为增强类,别忘了加上-->
    <aop:aspect ref="userProxy">
        <!--通知-->
        <aop:before method="before" pointcut-ref="p"/>
    </aop:aspect>
</aop:config>

3.4 切入点表达式的重用

抽取公共切入点表达式

@Pointcut("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void pointDemo() {
}

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

多个增强类对同一个方法增强,可以在类上加@Order(number)设置优先级,数字越小优先级越高,取值是自然数

3.5 完全注解开发

在配置类上加上以下注解。proxyTargetClass默认为false

@EnableAspectJAutoProxy(proxyTargetClass = true)

也是了解即可

4.1 JDBC Template

JDBC Template是spring对JDBC的封装

先引入新的一些依赖,方便之后使用

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.27</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.12</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.2.12.RELEASE</version>
</dependency>

4.2 配置DataSource

常用连接池:c3p0,Druid。用Druid演示一下

先引入依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

注入bean:先使用固定写法写死

<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
   <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://localhost:3306/test"/>
   <property name="username" value="root"/>
   <property name="password" value="root"/>
</bean>

一般将连接数据库的这些固定不变的value值写到配置文件中

jdbc.properties:

prop.driverClass=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.userName=root
prop.password=root

为了spring核心配置文件能载入properties配置文件,需要引入context命名空间,并用下面这种方式配置bean:

<context:property-placeholder location="classpath:jdbc.properties"/>
   <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="${prop.driverClass}"/>
      <property name="url" value="${prop.url}"/>
      <property name="username" value="${prop.userName}"/>
      <property name="password" value="${prop.password}"/>
   </bean>

4.3 配置JDBCTemplate,注入DataSource

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
   <property name="dataSource" ref="datasource"/>
</bean>

4.4 使用JDBCTemplate

创建service类和dao类,service类注入dao对象,dao类注入JDBCTemplate对象

dao实现类:

@Repository
public class BookDaoImpl implements BookDao{
    @Resource
    private JdbcTemplate jdbcTemplate;
}

service类:

@Service
public class BookService {
    @Resource
    private BookDao bookDao;
}

在dao的实现类里对数据库进行访问:

@Repository
public class BookDaoImpl implements BookDao{
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addBook(Book book) {
        String sql = "insert into book values(?,?,?)";
        Object[] args = {book.getId(),book.getName(),book.getPrice()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }
}

测试

@Test
public void test01(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    BookService bookService = context.getBean("bookService",BookService.class);
    Book book = new Book();
    book.setId(2);
    book.setName("英语");
    book.setPrice(20.0);
    bookService.addBook(book);
}

控制台

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPkbQs2v-1639306842620)(SSM.assets/image-20211211131534760.png)]

数据库

可以将id字段设为自增,每次插入不用插入id值

更新操作:

@Override
public void updateBook(Book book ,Integer id) {
    String sql="update book set name=?,price=? where id=?";
    Object[] args = {book.getName(),book.getPrice(),id};
    int update = jdbcTemplate.update(sql, args);
    System.out.println(update);
}

删除操作:

@Override
public void deleteBook(Integer id) {
    String sql="delete from  book where id=?";
    int update = jdbcTemplate.update(sql, id);
    System.out.println(update);
}

通过编号查询(或其他条件)queryForObject 返回值是对象

queryForObject()第一个和第三个参数是老相识了,第二个参数是RowMapper接口,而jdbctemplate已经帮我们实现了该接口,即BeanPropertyRowMapper,new出来这个实现类,泛型是查询结果的类型,参数是查询结果的类.class

@Override
public Book queryBook(Integer id) {
    String sql= "select * from book where id=?";
    Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
    return book;
}

查询全部 query 返回值是List集合

@Override
public List<Book> queryAll() {
    String sql="select * from book";
    List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
    return bookList;
}

批量增加

@Override
    public void batchAdd(List<Object[]> args) {
        String sql="insert book (name,price) values(?,?)";
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println(Arrays.toString(ints));
    }

测试代码

 List<Object[]> args = new ArrayList<>();
Object[] o1 = { "高等数学", 40.0};
Object[] o2 = { "线性代数", 20.0};
Object[] o3 = { "概率论", 29.8};
args.add(o1);
args.add(o2);
args.add(o3);
bookService.batchAdd(args);

批量修改和批量删除大同小异,不再赘述

5.1 处理事务

银行转账案例环境搭建(只贴必要代码)

dao

@Repository
public class AccountDaoImpl implements AccountDao{

    @Resource
    private JdbcTemplate jdbcTemplate;
    @Override
    public void addMoney() {
        String sql = "update account set money=money+? where name=?";
        jdbcTemplate.update(sql,100,"张三");
    }

    @Override
    public void reduceMoney() {
        String sql = "update account set money=money-? where name=?";
        jdbcTemplate.update(sql,100,"李四");
    }
}

service

@Service
public class AccountService {

    @Resource
    private AccountDao account;

    public void handleAccount(){
        account.reduceMoney();
        account.addMoney();
    }

}

事务管理分为编程式事务管理和声明式事务管理,spring采用后者,底层使用了AOP

现在配置文件中引入tx命名空间,并创建事务管理器,开启事务注解

<!--创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="datasource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

在service类上或方法上加@Transactional注解。

这样,service方法就会正常执行,出现异常会回滚

5.2 @Transactional的几个参数

propagation:事务传播行为

事务传播行为指事务方法(增删改等修改数据的方法)间调用时,事务是如何管理的

如A方法有事务,B方法没事务,如果A调用B,事务是怎么处理的

七种事务传播行为:

REQUIRED(默认):A有事务,B没事务,A调B,B用A事务;A没事务,B没事务,A调B,B新建事务

REQUIRED_NEW:无论A有无事务,A调B,B都新建事务,其他运行中的事务会被挂起

这两个用的较多。其他传播行为不再一一列举,去学数据库理论去

isolation:隔离级别

三个读问题:脏读、不可重复读、幻读

脏读:一个未提交事务读到另一个未提交事务的数据

不可重复读:读两次读到的数据不一致(别人修改数据并提交造成的)

幻读:读两次读到的表不一致(行数变化)

通过设置隔离级别解决上面的问题:(从前到后,隔离级别越来越高,解决问题越来越多,并发性越来越小)

READ_UNCOMMITTED读未提交。 没解决问题

READ_COMMITTED读已提交。 解决脏读

REPEATABLE_READ可重复读(默认) 解决脏读和不可重复读

SERIALIZABLE序列化 全部解决

timeout:超时时间

事务需要在一定时间内提交,超时则回滚,以秒为单位,默认值-1即不会超时

readOnly:是否只读

默认false。设为true则禁止事务增删改

rollBackFor

设置出现哪些异常会回滚

noRollBackFor

设置出现哪些异常不会回滚

注:基于配置文件的事务控制跳过,用AOP比较繁琐,平时一般用注解。纯注解方式声明事务也跳过了

6.1 Spring新特性

基于java8

自带了通用的日志功能

结合log4j2一起使用(不再支持版本1),引入依赖

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.15.0</version>
</dependency>

在log4j2.xml文件中进行一些基本设置:

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="DEBUG">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>
支持@Nullable注解

@Nullable可以加在方法上(表示返回值可以为空)、属性上(表示属性可以为空)、参数上(表示参数可以为空)

支持函数式风格
整合Junit5

先看如何整合junit4

引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.9</version>
</dependency>

用注解测试,方便省事。@RunWith(SpringJUnit4ClassRunner.class)写法固定。别忘了前面的Spring,没加的话报空指针

@ContextConfiguration(“classpath:bean.xml”)加载配置文件。需要测试的service对象也可以自动注入

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class JUnitTest {
    @Resource
    private  AccountService account;

    @Test
    public void test01(){
        account.handleAccount();
    }
}

整合JUnit5

引入依赖

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
</dependency>

第一个注解换了:

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JUnitTest {
    @Resource
    private  AccountService account;

    @Test
    public void test01(){
        account.handleAccount();
    }
}

可以两个注解二合一,更方便:

@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JUnitTest {
    @Resource
    private  AccountService account;

    @Test
    public void test01(){
        account.handleAccount();
    }
}

6.2 Spring WebFlux

spring5新添加的模块,用于web开发,功能与springmvc类似。异步(调用者发出请求后不是干等而是做其他事情)非阻塞(被吊用着收到请求立即反馈再开始任务),响应式编程,servlet3.1及以上

核心是基于Reactor的相关API实现的

特点:

非阻塞式:有限资源下提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程(RP)

函数式编程:基于java8,通过函数式编程实现路由请求
WebFlux可以单开一门课了,不再赘述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vogel_im_Kafig_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值