IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring笔记 -> 正文阅读

[Java知识库]Spring笔记

1、Spring

spring是一个轻量级、非入侵式的控制反转(IOC)和面向切面(AOP)的容器框架.

  • SpringBoot

    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速的开发单个微服务
    • 约定大于配置
  • SpringCloud

    • 基于SpringBoot实现的

控制反转 IoC

IoC是一种设计思想,而DI(依赖注入)只是实现 IOC的其中一种方法。

IoC是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection)

Hello,Spring!

  • 控制:谁来控制对象的创建!传统的应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的
  • 反转:程序本身不创建对象,而变成被冻的接收对象
  • 依赖注入:就是利用set方法进行注入
  • IOC是一种编程思想,由主动的编程变为被动的接收。对象由Spring来创建、管理、装配!

Pojo:

package com.shen.pojo;

import lombok.Data;

@Data
public class Hello {
    private String str;
}

Beans.xml:

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

    <!--使用spring来创建对象,在spring这些都称为bean-->
    <bean id="hello" class="com.shen.pojo.Hello">
        <property name="str" value="Hello,spring!"></property>
    </bean>
</beans>

Test:

import com.shen.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        // 获取spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 我们的对象都在spring中管理了,我们要使用,直接去里面取出来就可以
        Hello hello = (Hello)context.getBean("hello");
        System.out.println(hello.toString());
    }
}

IoC例子2:

ServiceImpl:

package com.shen.service;

import com.shen.dao.UserDao;
import lombok.Data;

@Data
public class UserServiceImpl implements UserService{
    private UserDao userDao;

    @Override
    public void printDaoInfo() {
        userDao.printInfo();
    }
}

beans.xml:

<!--使用spring来创建对象,在spring这些都称为bean-->
    <bean id="mysqlDao" class="com.shen.dao.UserDaoMysqlImpl"></bean>
    <bean id="oracleDao" class="com.shen.dao.UserDaoOracleImpl"></bean>
    <bean id="service" class="com.shen.service.UserServiceImpl">
        <!--
            ref: 引用Spring容器中创建好的对象
            value:具体的值,基本数据类型!
        -->
        <property name="userDao" ref="oracleDao"></property>
    </bean>

test:

public class MyTest {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserServiceImpl service = (UserServiceImpl)context.getBean("service");
        service.printDaoInfo();
    }
}

使用配置的好处在于,当需要修改set注入的内容的时候,不需要重启服务器,因为没有改动源代码,改的是xml配置!!而且解耦了,如果要增加新的实现,只需要写新的实现,并且替换配置。

IoC创建对象方式

  • 默认走的是无参构造

  • 可以使用有参构造

    • 1.按参数类型
    <bean id="service" class="com.shen.service.UserServiceImpl">
            <constructor-arg type="java.lang.String" value="有参构造的bean"></constructor-arg>
        </bean>
    

    缺点在于 如果有两个参数都是同类型,就不行了,所以不建议使用此法。(如果非要用的话,可以按顺序赋值)

    • 2.按照下标
    <constructor-arg index="0" value="有参构造的bean"></constructor-arg>
    
    • 3.匹配参数名
    <constructor-arg name="str" value="直接通过参数名赋值"></constructor-arg>
    

2、Spring配置

  1. 别名:alias,和bean是同一级别的标签。name,在bean标签内,也是别名,而且name可以取多个别名(用逗号、空格、分号进行分割)。

  2. import,和bean是同一级别的标签。一般用于团队开发使用,可以将多个配置文件导入合并为一个。

    <import resource="beans2.xml"/>
    

    注:使用import导入的时候,如果遇到同名的东西,后面的会覆盖前面的(因为按照从上到下的顺序进行导入合并)

  3. scope 在bean标签内,可以设置是否为单例模式(默认单例)

3、依赖注入

3.1 构造器注入

上面写过了

也可以用c命名空间

3.2 set方式注入(重点)

也可以用p命名空间

  • 依赖:bean对象的创建依赖于spring这个容器
  • 注入:bean对象中的所有属性,由容器注入
		<bean id="student" class="com.shen.pojo.Student" name="stu">
        <!--普通注入-->
        <property name="name" value="孟特"></property>
        <property name="id" value="4"></property>
        <!--对象注入(bean注入)-->
        <property name="address" ref="add"></property>
        <!--数组注入-->
        <property name="books">
            <array>
                <value>《西游记》</value>
                <value>《三国演义》</value>
            </array>
        </property>
        <!--list注入-->
        <property name="hobbies">
            <list>
                <value>弹吉他</value>
                <value>写代码</value>
            </list>
        </property>
        <!--map注入-->
        <property name="scopes">
            <map>
                <entry key="语文" value="90"></entry>
                <entry key="数学" value="100"></entry>
            </map>
        </property>
        <!--properties注入,key-value格式-->
        <property name="info">
            <props>
                <prop key="学号">201920085211015</prop>
                <prop key="sex"></prop>
            </props>
        </property>
    </bean>
    <bean id="address" class="com.shen.pojo.Address" name="add">
        <constructor-arg name="addressName" value="梆子井4503"></constructor-arg>
        <!--set注入-->
        <!--测试注入一个空字符串,和一个null-->
        <property name="concludes">
            <set>
                <value>北京市</value>
                <value>朝阳区</value>
                <value></value>
                <value>null</value>
                <null></null>
                <value>三里屯</value>
            </set>
        </property>
    </bean>

3.3 拓展方式注入

p命名空间、c命名空间

需要引入第三方xml约束:

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

3.4 Bean scope (作用域)

  • Singleton 单例

    • 每次从容器中get的时候都会返回同一个对象
  • prototype 原型

    • 每次从容器中get的时候都会产生一个新的对象,其实这就是原型模式

    下面的这些只有在web中才能用

  • request

  • session

  • websocket

  • Application

4、Bean的自动装配(autowired)

spring不仅可以手动依赖,还可以实现自动依赖!

spring会在上下文中自动寻找,并且给bean装配属性

有三种装配的方式

  • 在xml中显示配置
  • 在java中显示配置
  • 隐式的自动配置

自动装配的实现方法:

  • byName:会自动在容器上下文中查找,自己对象set方法后面的值对应的beanid

    ? 比如,当前bean对象有一个方法叫setCat,就会自动寻找一个id叫做的cat的bean对象,把它装配进去

  • byType:会自动在容器上下文中查找,与自己对象属性类型相同的bean

    • 弊端:必须保证每个类型的实体全局唯一

5、使用注解进行自动装配

要使用注解须知:

  1. 导入约束
  2. 配置注解的支持
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
               https://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/context
               https://www.springframework.org.schema/context/spring-context.xsd">

  <context:annotation-config/>
</beans>

例:

<bean id="dog" class="com.shen.pojo.Dog" >
        <property name="name" value="wang~"></property>
    </bean>
    <bean id="cat" class="com.shen.pojo.Cat" >
        <property name="name" value="miao~"></property>
    </bean>
    <bean id="people" class="com.shen.pojo.People" />
public class People {
    @Autowired
    private Dog dog;
    @Autowired
    private Cat cat;
    private String name;

    @Override
    public String toString() {
        return "People{" +
                "dog=" + dog +
                ", cat=" + cat +
                ", name='" + name + '\'' +
                '}';
    }
}

我们发现:没有set方法,仅使用注解,也可以成功注入!!这是为什么呢?

因为使用注解的时候,不是通过set注入,而是通过反射直接获取了变量名以后进行注入

小结

@Autowired既可以直接在属性上使用,也可以在set方式上使用。

@Autowired(required = false) 可以为null

科普

@Nullable 字段标记了这个注解,则这个字段可以为null而不报错!

如果自动装配环境比较复杂,无法通过一个注解完成的时候(比如一个类有多个bean实例),我们可以在@Autowired后面接一个@Qualifier(value = “dog111”) 来指定唯一的bean对象。

也可以不使用两个注解,而只使用@Resource(name = “xxx”),这个是java自带的注解,先通过名字匹配byName,不唯一的话再通过类型匹配byType。都找不到就报错了。

6、使用注解开发

在Spring4之后,要使用注解开发,必须保证aop的包已经导入

  1. bean

    package com.shen.pojo;
    
    import lombok.Data;
    import org.springframework.stereotype.Component;
    
    // @Component等价于 <bean id="user" class="com.shen.pojo.User"/>
    @Data
    @Component
    public class User {
        private String name;
    }
    
  2. 属性如何注入

    @Component
    public class User {
        // @Value 相当于bean里的property的value
        @Value("沈航冉")
        private String name;
    }
    
    
  3. 衍生的注解

    @Component有几个衍生注解,在我们的web开发中,会按照mvc三层架构分层

    • dao【@Repository】 功能是一样的,但我们习惯用@Repository给dao层的东西加注解,也代表它是一个组件(component)

    • service【@Service】

    • controller【@Controller】

      这四个注解的功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

  4. 自动装配

  5. 作用域

    @Scope(“prototype”)

    采用原型模式(默认为singleton)

  6. 小结

    • xml更加万能,适用于任何场合,维护方便简单
    • 注解 不是自己的类不能用,维护相对复杂

    最佳实践:xml用来管理bean,注解只负责使用的注入。

    使用注解记得需要开启注解的支持。

7、使用Java的方式配置Spring

现在我们不使用Spring的xml配置了,全权交给java来做。

JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。

@Data
@Component
public class User {
    @Value("沈阳的沈")
    private String name;
}

// 这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml一样的
@Configuration
public class ShenConfig {
  // 注册一个bean 就相当于我们之前写的一个bean标签 默认单例
    @Bean
    public User getUser(){
        return new User();
    }
}
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ShenConfig.class);
        User getUser = context.getBean("getUser", User.class);
        System.out.println(getUser);
    }
}

8、代理模式

和装饰器模式的语法完全一样,只是语意不一样!

SpringAOP的底层就是代理模式.

8.1、静态代理

代码步骤:

  1. 公共接口
  2. 真实角色
  3. 代理角色
  4. 客户端访问代理角色

和装饰器模式完全相同。

  • 可以使真实角色更加纯粹
  • 公共的事情交给代理角色来做

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aW48FLto-1625239701232)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210629162314168.png)]

原来是纵向开发,在不改动原有DAO层代码的时候,想给DAO层加一个日志功能,就可以采取和Service层并列的新建一个代理模式的类来完成,这就是横向开发。

8.2、动态代理

动态代理的代理类是动态生成的,不是我们直接写好的。

动态代理分为两大类:

  • 基于接口的动态代理
    • JDK动态代理【接下来使用这个】
  • 基于类的动态代理
    • cglib
    • java字节码实现:javasist

需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)

例子:

用来生成代理类(对象)的类,详情看注释:

package shen;

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

// 我们会用这个类来自动生成代理类----动态代理
public class ProxyInvocationHandler implements InvocationHandler {
    // 被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    // 生成得到代理类
    public Object getProxy(){
        // 三个参数:生成的代理类【放到哪】、【代理的那个类是什么类型的接口】、【如何生成(使用重写的invoke方法)】
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果(官方原话)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理的本质,就是使用反射机制!
        // 这里的rent是被代理的那个类
        Object invoke = method.invoke(rent, args);
        // 在这里使用中介独有的新增加的方法
        seeHouse();
        fare();
        return invoke;
    }

    // 中介独有方法:带客户看房子
    public void seeHouse(){
        System.out.println("中介带用户看房子");
    }
    public void fare(){
        System.out.println("中介收你中介费");
    }
}

代码步骤:

  1. 设置要代理的类是谁,给它写一个set方法
  2. 利用要代理的类,生成得到代理类
  3. 处理代理实例,返回结果(添砖加瓦)

客户这样使用:

public class Client {
    public static void main(String[] args) {
        Host host = new Host(); // 真实角色
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); // 代理角色
        proxyInvocationHandler.setRent(host);
        Rent proxy = (Rent)proxyInvocationHandler.getProxy(); // 这里的proxy就是动态生成的,我们并没有写一个真实的代理类
        proxy.rrrrent();
    }
}

于是,我们发现了,上面写的ProxyInvocationHandler类==可以当做一个工具类了,因为它可以代理任何一个类呀!==比如要增加日志功能,只需要自顶一个log方法,并放到invoke方法里使用就可以了!

public void log(String msg){
        System.out.println("[Debug]执行了"+msg+"方法");
    }

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(rent, args);
        log(method.getName());
        return invoke;
    }

通过method.getName()反射可以获取方法名呀!

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用关注一些公共的业务(比如log)
  • 公共业务可以交给代理角色,实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要实现了同一个接口就行,复用成本低。

使用spring实现aop,需要导入一个依赖包

<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>

8.3 使用spring的aop

方式1:使用原生API接口

写一个接口和一个要被代理的类,最终目的是给它增加日志功能。

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("使用了add方法");
    }

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

    @Override
    public void update() {
        System.out.println("使用了update方法");
    }

    @Override
    public void query() {
        System.out.println("使用了query方法");
    }
}

使用原生API接口,定义在某方法执行前,我们要做的事情

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    // method: 要执行的目标对象的方法
    // objects:参数
    // o:目标对象
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行啦!");
    }
}

使用xml配置aop,定义切入点(在哪里执行log方法),并且定义执行环绕增加(log方法是谁)

<bean id="userService" class="com.shen.UserServiceImpl"/>
<bean id="log" class="com.shen.log.Log"/>

    <!--方式一:使用原生API接口-->
    <!--配置aop-->
    <aop:config>
        <!--切入点:在哪个地方执行方法 , expression:表达式,execution(要执行的位置!* * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.shen.UserServiceImpl.*(..))"/>
        <!--执行环绕增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    </aop:config>

测试

注意,我们动态代理代理的是接口,而不是具体实现类,所以如果写成具体实现类会报错!

import com.shen.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 动态代理代理的是接口,而不是实现类!
//        UserServiceImpl userservice = context.getBean("userService", UserServiceImpl.class);
        UserService userservice = context.getBean("userService", UserService.class);
        userservice.delete();

    }
}

方式2:使用自定义类实现AOP

package com.shen.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("===方法执行前===");
    }
    public void after(){
        System.out.println("===方法执行后===");
    }
}

将自定义类当做切面,配置到aop中

<!--方式二:自定义类-->
    <bean id="diy" class="com.shen.diy.DiyPointCut"/>
    <aop:config>
        <!--自定义切面,ref:要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.shen.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

很明显,第一种方法功能要强大的多。因为第一种用到了反射。

方式3:使用注解实现

自定义切面类,在上面使用注解

补充:环绕增强,详见代码。

package com.shen.diy;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 方式三:使用注解方式实现AOP
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.shen.UserServiceImpl.*(..))") // 注解代表通知,其参数代表切入点
    public void before(){
        System.out.println("注解实现的方式执行之前");
    }
}
// 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点:
    @Around("execution(* com.shen.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("===环绕前===");
        // 获得签名
        Signature signature = proceedingJoinPoint.getSignature();
        System.out.println("签名:"+signature);
        // 执行方法
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("===环绕后===");
    }

然后将这个类配置到xml中,成为bean对象。最后,开启注解支持

<!--方式三:使用注解实现AOP-->
    <bean id="annotationPointCut" class="com.shen.diy.AnnotationPointCut"/>
    <!--开启注解支持!-->
    <aop:aspectj-autoproxy/>

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IUg3Pl4g-1625239701234)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210630231832789.png)]

8.4 名词总结

  • 横切关注点:与业务逻辑无关的,我们要实现的功能的概述。比如“日志、安全、缓存等等”
  • 切面(aspect):横切关注点被模块化的特殊对象。即,一个类
  • 通知(advice):切面要完成的工作。即,一个方法
  • 目标(target):被通知对象。即,被代理的类对象。
  • 代理(proxy):向目标对象应用通知后创建的对象。即,使用动态代理模式创建出的代理类对象。
  • 切入点(pointcut):切面通知执行的“地点”的定义。即,在哪里执行“通知”这个方法。
  • 连接点(jointpoint):与切入点匹配的执行点。

9、整合Mybatis

步骤:

  1. 导入相关jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关的
    • aop织入
    • mybatis-spring
    <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.25</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.8</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.3.7</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.7</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.2</version>
            </dependency>
        </dependencies>
    
  2. 编写配置文件

  3. 测试

整合步骤:

将spring的配置和mybatis的配置都整合到spring中,首先需要创建一个spring-dao.xml

其实本质就是把mybatis里的datasource、sqlSessionFactory和sqlSession交给spring托管

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
               https://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/context
               https://www.springframework.org/schema/context/spring-context.xsd
               http://www.springframework.org/schema/aop
               https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--DataSource: 使用spring的数据源替换Mybatis的配置   c3p0  dbcp
    在这里使用spring提供的jdbc
    -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=TRUE&amp;useUnicode=TRUE&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="jiawensili1029"/>
    </bean>
    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config2.xml"/>
        <property name="mapperLocations" value="classpath:com/shen/mapper/*.xml"/>
    </bean>

    <!--这个SqlSessionTemplate相当于原来的sqlSession,template就是模板的意思。这个类需要注入一个sqlSession工厂-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用构造器注入,因为它没有set方法-->
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

    <bean id="userMapper" class="com.shen.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

</beans>

其中,userMapper是自定义的一个接口的实现类,它的作用就是,把sqlSession注入进来,然后使用它!而它本身又成为一个bean对象,方便我们spring来托管。

package com.shen.mapper;

import com.shen.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper{
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<User> getAllUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.getAllUser();
    }
}

在配置文件中,我们注意下面这段:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <!--绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config2.xml"/>
        <property name="mapperLocations" value="classpath:com/shen/mapper/*.xml"/>
    </bean>

其中,mapperLocations包含了所有mapper.xml,比如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "htto://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shen.mapper.UserMapper">

    <select id="getAllUser" resultType="user">
        select * from user_test
    </select>
</mapper>

我们的sql语句就是在这里写的!

而configLocation则是原来mybatis的配置文件。

由于原来mybatis的配置文件有很多内容已经集合到spring-dao.xml里了,所以现在的mybatis配置文件只剩下“别名”和“设置”这两部分内容!

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "htto://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心-->
<configuration>

    <!--设置要留着,可以添加其他设置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <typeAlias type="com.shen.pojo.User" alias="user"/>
    </typeAliases>

</configuration>

如果没有别名的话,写sql会比较麻烦,而设置里我们可以添加其他的设置,比如日志。

此外,由于除了userMapper这个bean对象以外,其他都是固定的,因此我们可以上面一部分内容单独拿出来固定在一个xml里,再新建一个总的application.xml,把上面那个xml通过resource的形式引入,这样就不会显得乱了!(解耦了)

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
               https://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/context
               https://www.springframework.org/schema/context/spring-context.xsd
               http://www.springframework.org/schema/aop
               https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--DataSource: 使用spring的数据源替换Mybatis的配置   c3p0  dbcp
    在这里使用spring提供的jdbc
    -->
    <import resource="spring-dao.xml"/>

    <bean id="userMapper" class="com.shen.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

</beans>

注:sqlSessionTemplate是线程安全

10、声明式事务

    @Override
    public List<User> mixFixed() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User("shr", "shrshrshr", "沈航冉"));
        mapper.deleteUser("shr");
        List<User> allUser = mapper.getAllUser();
        return allUser;
    }
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserMapperImpl userMapper = context.getBean("userMapper", UserMapperImpl.class);
        List<User> allUser = userMapper.mixFixed();
        for(User user:allUser){
            System.out.println(user);
        }
    }

经过测试,我们明明把好几个任务混合起来,当做了一个“新的任务”,它是一个新的原子性,应该当做一个事务来处理,但我们这么跑下来,虽然程序会报错(我们故意写错了delete的sql语句),但是还是成功插入了一组数据,说明这个方法并不是满足ACID原则的,说明它没有完成事务的功能!

为此,我们要使用声明式事务。

补充:

  • 声明式事务:AOP
  • 编程式事务:需要在代码中进行事务的管理(使用try、catch、rollback等)

声明式事务需要在spring的配置文件里做,本质就是spring帮我们写好了一个事务管理器。

    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"/>
    </bean>

切入点处的方法已经被tx实现了,所以我们导入tx依赖,就可以写成下面这样.

tx的作用就是实现了事务处理的一些方法,我们要把这些方法配置到使用的地方(切入点)

tx这一套东西相当于是一个切面(理解为一个bean对象,所以可以使用advice-ref来增加执行环绕)

		<!--结合AOP实现事务的织入-->
    <!--配置事务通知的类(需要在最上面导入tx)-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务-->
        <!--配置事务的传播特性:propagation-->
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <!--查找不需要事务-->
            <tx:method name="query" read-only="true"/>
            <!--*代表全部-->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txpointcut" expression="execution(* com.shen.mapper.*.xml.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/>
    </aop:config>
  • 补充:配置事务的传播特性(七种):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prPOjvAk-1625239701235)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702205923362.png)]

经测试,发现执行mixFixed方法还是把东西插进去了,找到原因:

  • 因为mixFixed方法并没有在mapper中注册,而只是在UserMapperImpl里写明了它是把好几个事情混在一起的方法,我们的配置事务作用域并没有包含UserMapperImpl,而只包含了所有的mapper底下的所有的xml里的东西。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQLRfdyP-1625239701236)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702213239187.png)]

  • 解决方案:

    1. 改为mapper底下所有文件,而不只是xml文件

      <!--配置事务切入-->
          <aop:config>
              <aop:pointcut id="txpointcut" expression="execution(* com.shen.mapper.*.*(..))"/>
              <aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/>
          </aop:config>
      
    2. 使用的地方,统一为UserMapper.class,而不是UserMapperImpl.class

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Elvyeevm-1625239701237)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702214034558.png)]

      不然会报错:

      Bean named ‘xxx’ is expected to be of type ‘xxx’ but was actually of type 'com.sun.proxy.$Proxy14

    3. tx中配置的name必须与方法名相同

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAZW157X-1625239701238)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702214300647.png)]

经测试,此处注释掉mixFixed会导致失败.

当然,也可使直接使用*来匹配所有.

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-04 19:22:40  更:2021-07-04 19:23:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 11:07:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码