Java-Spring 学习笔记


启航

1.1、简介

  • 2002年,首次推出了雏形:interface21框架
  • 2004年以interface21为基础重新设计,发布了1.0正式版
  • 由Rod Johnson 开发,是音乐学的学生
  • spring理念:使现有技术更容易使用,整合现有技术框架
  • SSH:Struct2 + Spring + Hibernate
  • SSM: SPringMvc + Spring + Mybatis (半自动,可定制很高)

仓库(全版本):https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-local/org/springframework/spring/5.3.3

文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference

spring meven 地址:https://mvnrepository.com/search?q=spring 导入Spring Web MVC

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.4</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.4</version>
</dependency>

1.2、优点

  • spring 是开源的框架!
  • spring 是一个轻量级的、非入侵式框架。
    • 非入侵:引入后不会原有项目进行印象
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

spring是一个轻量级的控制反转和面向切面编程的框架

1.3、组成

1.4、其他

spring:现代化的Java 开发,基于Spring的开发!

  • Spring Boot
    • 是一个快速开发脚手架
    • 基于 Springboot 可快速开发单个微服务
    • 约定大于配置!
  • Spring Cloud
    • Spring Cloud是基于SpringBoot实现的
    • 公司稍大需要使用流程控制,需要使用Spring Cloud

学习SpringBoot的前提:完全掌握Spring及SpringMVC!

弊端:配置十分繁琐

IOC 推导

2.1、自动导入包

2.2、新建一个spring项目

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo2</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>spring2-22</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.4</version>
        </dependency>

    </dependencies>
</project>

2.3、传统开发流程在java表现

2.3.1、起始状态

传统开发流程

  1. UserDao 接口
  2. UserDaolmpl 实现类
  3. UserService 业务接口
  4. UserServiceImpl 业务实现类
// UserDao.java I 

package com.hui.dao;

// 这是一个接口
public interface UserDao {
    void getUser();
}
// UserDaoImpl.java C
package com.hui.dao;

// 实现类实现接口
public class UserDaoImpl implements UserDao {
    public void getUser() {
        System.out.println("123213");
    }
}
// UserService.java I
package com.hui.service;

// 实现类
public interface UserService {
    void getUser();
}
// UserServiceImpl.java C
package com.hui.service;

import com.hui.dao.UserDao;
import com.hui.dao.UserDaoImpl;

// 核心:业务层调dao层
public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    // 业务层调dao 层
    public void getUser() {
        userDao.getUser();
    }
}

用户使用层

// test/java/my2221.java
import com.hui.service.UserServiceImpl;

public class my2221 {
    public static void main(String[] args) {
        // new 业务层,而不是dao层
        // 用户实际操作的地方,dao层用户不需要接触

        UserServiceImpl userService = new UserServiceImpl();
        userService.getUser();
    }
}

2.3.2、新增业务

在原来的基础上新增业务

// UserDaoMysqlImpl.java  C
package com.hui.dao;

public class UserDaoMysqlImpl implements UserService {
        public void getUser() {
        System.out.println("get mysql data!");
    }
}

需要重新实例化对象:

  • 因为客户的需求每次都需要改原有的代码
  • 代码量大有问题
  • 程序无法适应用户变化
// UserServiceImpl.java C
package com.hui.service;

import com.hui.dao.UserDao;
import com.hui.dao.UserDaoImpl;
import com.hui.dao.UserDaoMysqlImpl;

public class UserServiceImpl implements UserService{

    // 重新实例化目标:需要修改代码 
    // UserServiceImpl userService = new UserServiceImpl();
    private UserDaoMysqlImpl userDao = new UserDaoMysqlImpl();

    public void getUser() {
        userDao.getUser();
    }
}

2.3.3、使用set动态注入

//UserDaoImpl.java c
package com.hui.service;

import com.hui.dao.UserDao;
//核心:业务层调dao层
public class UserServiceImpl implements UserService{

    private UserDao userDao;
    // 利用set进行动态值注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    // 业务层调dao 层
    public void getUser() {
        userDao.getUser();
    }
}
// test/java/my2221.java
import com.hui.dao.UserDaoImpl;
import com.hui.service.UserService;
import com.hui.service.UserServiceImpl;

public class my2221 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();

        
        ((UserServiceImpl)userService).setUserDao(new UserDaoImpl());
        // 或者
        // ((UserServiceImpl)userService).setUserDao(new UserDaoMysqlImpl());
        
        userService.getUser();
    }
}

使用set注入后,程序不再具有主动性,而是变成了被动的接收对象。这就叫控制反转

  • 之前需要修改业务代码,而使用set后,只需要修改用户层代码(test/java/my2221.java)
  • 系统耦合大大降低,使开发更加专注在业务实现上

2.4、IoC的本质

IoC(控制反转)是一种设计思想DI(依赖注入)是实现IoC的一种方法

  • 控制反转:获得依赖对象的方式反转的,业务的对象:由程序员变成用户

控制反转是一种通过描述(XML或注解)通过第三方去生产或获取特定对象的方式。

在spring中实现控制反转的是IoC容器,其实现方法是依赖注入(DI)

Hello spring

3.1、使用bean 配置托管

  1. 新建hello类
package com.hui.dao;

public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
    @Override
    public String toString() {
        return "helo:" + str;
    }
}
  1. 在resource中新建beans.xml
  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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>
  1. 修改下,讲hello.java进行托管
<?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">
	<!-- 这里先这样写,后面会说明为什么 -->
    <bean id="Hello" class="com.hui.dao.Hello">
        <!-- 这里的name 指定的是之前需要set 的那个的变量名,value为 常规值-->
        <property name="str" value="hellow"></property>
    </bean>
    
</beans>
  1. 加载配置文件并使用
// Mytest.java 
import com.hui.dao.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");
        Hello hello = (Hello)context.getBean("Hello");
        System.out.println(hello.toString());
    }
}

类型 变量名 = new 类型()

Hello hello = new hello()

id = 变量名

class = new 的对象

property 给对象中的属性

控制:传统的程序对象是由程序本身创建的,使用spring后,对象由spring来创建

反转:程序本身不创建对象,变成被动的接收对象

依赖注入:利用set方法来进行注入

IoC:由主动的变成变成被动的接收

❗:对象由spring来创建,管理,装配!

3.2、修改原有项目给bean托管

2.3的项目使用bean托管

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

    <bean id="MysqlImpl" class="com.hui.dao.UserDaoMysqlImpl" />
    <bean id="UserImpl" class="com.hui.dao.UserDaoImpl" />

    <bean id="UserServiceImpl" class="com.hui.service.UserServiceImpl">
        <!--
        ref : 的意思是引用 spring中的对象,在上文中id为 mysqlImpl 的bean对象
        value : 具体的值,基本对象类型
        -->
        <property name="userDao" ref="MysqlImpl"></property>
    </bean>
</beans>

my2221.java的修改

import com.hui.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class my2221 {
    public static void main(String[] args) {
       // CPA + tab 就可以快速创建
       // 拿到 spring 的容器
         ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
         UserServiceImpl userService = (UserServiceImpl)context.getBean("UserServiceImpl");

         userService.getUser();
    }
}

用户如果需要修改项目需求,则直接在配置中更改,而不用更改源码

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

    <!-- 实现类托管 -->
    <bean id="UserDaoImpl" class="com.hui.dao.UserDaoImpl" />
    <bean id="UserDaoMysqlImpl" class="com.hui.dao.UserDaoMysqlImpl" />
	
    <!-- 业务接口托管 -->
    <bean id="UserServiceImpl" class="com.hui.service.UserServiceImpl" >
         <!--
        ref: 的意思是引用 spring中的对象,在上文中id为 mysqlImpl 的bean对象
        value: 具体的值,基本对象类型
        -->
        <property name="userDao" ref="UserDaoMysqlImpl" />
        
        <!--
			切换业务只需要修改当前配置,由UserDaoMysqlImpl 切换为 UserDaoImpl
        <property name="userDao" ref="UserDaoImpl" />
		-->
    </bean>
</beans>

用户切换业务只需要修改配置文件,你可以写一个脚本修改配置文件的ref引用

  • 系统耦合性降低
  • 开发者可以更加注重服务开发

IoC创建对象方式

  • 使用 无参构造方式 创建

4.1、测试使用无参构造

  1. 新建测试user类
package com.hui.projo;

public class User {
    private String name;

    // 无参构造
    public User() {
        System.out.println("user 无参构造!");
    }

    public String getName() {
        return name;
    }

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

    public void show() {
        System.out.println("name" + name);
    }
}
  1. bean文件托管(beans.xml)
<bean id="projo" class="com.hui.projo.User">
  1. 加载配置文件,并使用
public class spring22272 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("projo");
    }
}
  1. 删除无参构造后,程序报错

4.2、有参构造创建

  • 通过构造器注入

    手册中找到 Constructor Argument Resolution

测试代码

package com.hui.projo;

public class User {
    private String name;

    // 有参构造
    public User(String name) {
        this.name = name;
    }

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

    public void show() {
        System.out.println("name" + name);
    }
}

// ============使用============
public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("projo");
        user.show();
}

4.2.1、下标方式

通过下标去取的原因:没有构造器

<bean id="projo" class="com.hui.projo.User">
    <!-- 下标为 0 的参数赋值 -->
	<constructor-arg index="0" value="!!!one value!!!" />
</bean>

4.2.2、类型赋值(不建议使用)

<bean id="projo" class="com.hui.projo.User">
    <!-- 指定为java中的String 类型 
		如果两个参数都为String 则会报错
	-->
	<constructor-arg type="java.lang.String" value="hhhh" />
</bean>

4.2.3、直接参数赋值

<bean id="projo" class="com.hui.projo.User">
    <constructor-arg name="name" value="通过name赋值,推荐" />
</bean>

在配置文件加载时,所有容器内都被实例化

Spring 配置文件

id: bean唯一标识符

class:bean中 对象对应的全限定名:包路径+类

name:(bean 中)也是别名,可以同时取多个,可以用逗号、分号、空格分隔

5.1、别名

<bean id="projo" class="com.hui.projo.User">
    <constructor-arg name="name" value="123" />
</bean>
<!-- 原有、新名字均可以使用 -->
<alias name="projo" alias="newProjo" />


<!-- 给bean 设置name,也能达到别名效果 -->
<bean id="projo" class="com.hui.projo.User" name="one two,four;five">
    <constructor-arg name="name" value="123" />
</bean>

5.2、导入

导入后可以通过beans中找到所有容器

<import resource="bean1.xml" />
<import resource="bean2.xml" />

依赖注入

6.1、使用有参、无参构造器注入

详见:4.1 和 4.2

6.2、set方式注入

  • 依赖:bean 对象的创建依赖于容器
  • 注入:bean 对象中所有属性由容器来注入

参见手册Collections,1.4.2

6.2.1、所有java类型注入演示

// studnet.java c
import java.util.*;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
     
    //省略所有的get、set 方法
     
    @Override
    public String toString() {
        return "Student{" + '\n' +
            "name='" + name + '\'' + '\n' +
            "address=" + address.toString() + '\n' +
            "books=" + Arrays.toString(books) + '\n' +
            "hobbys=" + hobbys + '\n' +
            "card=" + card + '\n' +
            "games=" + games + '\n' +
            "wife='" + wife + '\'' + '\n' +
            "info=" + info + '\n' +
            '}';
    }
 }
// Address.java C
public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
}
<!-- 类托管 -->
<bean id="Address" class="com.hui.pojo.Address" >
    <property name="address" value="北京" />
</bean>

<bean id="Student" class="com.hui.pojo.Student">
    <!-- 普通值注入 -->
    <property name="name" value="名字"/>

    <!-- Bean注入,使用ref -->
    <property name="address" ref="Address" />

    <!-- 数组注入 -->
    <property name="books">
        <array>
            <value>《书本1》</value>
            <value>《书本2》</value>
            <value>《书本3》</value>
        </array>
    </property>

    <!-- list注入 -->
    <property name="hobbys">
        <list>
            <value>唱歌</value>
            <value>跳舞</value>
            <value>玩游戏</value>
        </list>
    </property>

    <!-- Map注入 -->
    <property name="card">
        <map>
            <entry key="银行卡1" value="1231123123123123" />
            <entry key="身份证" value="123123123123123123" />
        </map>
    </property>

    <!-- Set注入 -->
    <property name="games">
        <set>
            <value>game1</value>
            <value>game2</value>
            <value>game3</value>
        </set>
    </property>

    <!-- 指针 两种方式 NULL
        <property name="wife" value="" />
        -->
    <property name="wife">
        <null />
    </property>

    <!--Properties 它是JAVA中的属性操作类-->
    <property name="info">
        <props>
            <prop key="学号">123123123123</prop>
            <prop key="性别">无性</prop>
            <prop key="姓名"></prop>
        </props>
    </property>
</bean>
// 测试类
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) context.getBean("Student");
    System.out.println(student.toString());
}

6.3、扩展注入

手册中 的 1.4.2

P命名和C命名不能直接使用,需要在xml文件中额外导入

<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:c="http://www.springframework.org/schema/c"
       
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<!--
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
-->

导入junit 包,写一个测试类

import com.hui.pojo.Student;
import com.hui.pojo.User;
// 引入包,详见本文 技巧 部分
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class di0228 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("Student");
        System.out.println(student.toString());
    }

    @Test
    public void test2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
        // 如果在getBean第二个参数指定为类,就不需要强转
        User user = context.getBean("User", User.class);
        System.out.println(user);
    }
}
// User.java C
package com.hui.pojo;

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

6.3.1、P命名注入

p是property 的缩写,等同于使用 property 注入

<?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"
    xmlns:p="http://www.springframework.org/schema/p"
    >
    <!-- 使用p命名空间 -->
    <bean id="User" class="com.hui.pojo.User" p:age="18" p:name="张三" />
	
    <!-- 不使用p命名空间 -->
    <bean id="User" class="com.hui.pojo.User">
        <property name="name" value="张三" />
        <property name="age" value="18" />
    </bean>
</beans>

6.3.2、C命名注入

c 是constructor 的缩写,等同于使用 constructor (构造器)注入

需要使用有类构造器注入

// 在user.java中添加
public User(String name, int age) {
    this.name = name;
    this.age = age;
}
<?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"
        xmlns:c="http://www.springframework.org/schema/c"
    >
    <!-- 使用p命名空间 -->
    <bean id="User" class="com.hui.pojo.User"  c:age="18" c:name="张三"/>
    
    <!-- 不使用p命名空间 -->
    <bean id="User" class="com.hui.pojo.User">
        <constructor-arg name="age" value="18" />
        <constructor-arg name="name" value="张三" />
    </bean>
</beans>

6.4、Bean

6.4.1、Bean的作用域

参见手册 1.5 Bean Scopes

  • 单例模式:都使用同一个对象
  • 原型模式:每次从容器中get的时候都会产生新对象
  1. 单例模式

默认情况下是“单例”,不加就是默认

<bean id="User" class="com.hui.pojo.User"  scope="singleton"/>
  1. 原型模式(多线程使用)
<bean id="User" class="com.hui.pojo.User" scope="prototype" />
  • request 在http中请求时创建
  • session 一段时间内存在
  • application 全局存在

6.4.2、bean的自动装配

手册的1.9(.2)中

spring中的三种装配模式:

  1. 在xml中配置
  2. java中显示配置
  3. 隐式的自动装配

6.4.3、环境配置

代码逻辑:一个人有两个宠物

// People.java C
package com.hui.pojo;

public class People {
    private Cat cat;
    private Dog dog;
    private String name;

   	// 省略set,get,tostring方法
}

// Dog.java C
package com.hui.pojo;

public class Dog {
    public void shout() {
        System.out.println("汪~");
    }
}

// Cat.java C
package com.hui.pojo;

public class Cat {
    public void shout() {
        System.out.println("喵~");
    }
}

传统beans配置:

<bean id="Cat" class="com.hui.pojo.Cat"/>
<bean id="Dog" class="com.hui.pojo.Dog" />

<bean id="People" class="com.hui.pojo.People">
<!-- 没一个都需要手动赋值 很麻烦 -->
    <property name="cat" ref="Cat" />
    <property name="dog" ref="Dog" />
    <property name="name" value="hui3c"/>
</bean>

6.4.4、根据名称自动装配

会在容器上下文中查找,和自己set方法后面的值对应的beanid!区分大小写!必须保证所有bean id 唯一

<bean id="cat" class="com.hui.pojo.Cat"/>
<bean id="dog" class="com.hui.pojo.Dog" />

<bean id="People" class="com.hui.pojo.People" autowire="byName">
    <property name="name" value="hui3c" />
    <!--
 		自动填补,根据  托管类的id,和所需要 类set的名字
    <property name="cat" ref="Cat" />
    <property name="dog" ref="Dog" />
    -->
</bean>

6.4.5、根据类型自动装配

会在容器上下文中查找,和自己对象属性类型相同的bean!这个类型(class)必须全局唯一

若两个配置都存在,则先找byType再找byName

<!-- 注意,id没了 是根据类来绑定-->
<bean class="com.hui.pojo.Cat"/>
<bean class="com.hui.pojo.Dog" />

<bean id="People" class="com.hui.pojo.People" autowire="byType">
    <property name="name" value="hui3c" />
</bean>

6.4.6、注解模式 @Autowired

  1. 需要导入约束context
  2. 需要配置注解的支持
<!-- 赋值bean后改成context,再把目标链接那两个也复制一遍,把bean改成context -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        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="people" class="com.hui.pojo.People" />
<bean id="cat" class="com.hui.pojo.Cat" />
<bean id="dog" class="com.hui.pojo.Dog" />

在需要自动装配的变量前加入注解器 @Autowired ,(添加后可以把set方法去了):

package com.hui.pojo;

import org.springframework.beans.factory.annotation.Autowired;

public class People {
    // 注解器加入后
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    
    private String name;

	// 省略get方法。set方法会通过反射自动生成
}

也可以添加在set方法前(官网上是这样推荐的)

private Cat cat;
private Dog dog;
private String name;

@Autowired
public void setCat(Cat cat) {
    this.cat = cat;
}

@Autowired
public void setDog(Dog dog) {
    this.dog = dog;
}

6.4.7、注解模式 @Qualifier

类型重复后,使用qualifier 来确定具体哪一个

  1. xml中类型重复(有多个)
<bean id="people" class="com.hui.pojo.People" />
<bean id="dog" class="com.hui.pojo.Dog" />

<bean id="cat2" class="com.hui.pojo.Cat" />
<bean id="cat3" class="com.hui.pojo.Cat" />
<bean id="cat4" class="com.hui.pojo.Cat" />
  1. 使用qualifier 指定是哪个
private Cat cat;

@Autowired
@Qualifier(value = "cat2")
public void setCat(Cat cat) {
    this.cat = cat;
}

类型必须相同,不能说把把“狗”引用给“猫”。@Autowired@Qualifier 是一套

如果字段使用了 @Nullable 标记注解,说明这个字段可以为null

6.4.8、注解模式 @Resource

这个注解器是java自带的。继承了上面两个注解器的功能,但没有idea右边的那个叶子提示

不加参数情况下,先找name,再找类型。如果name 与 类名小写 不符,且类型重复则会抛出报错

@Resource
public void setCat(Cat cat) {
    this.cat = cat;
}
<!-- id不符合,类型不唯一:报错 -->
<bean id="cat2" class="com.hui.pojo.Cat" />
<bean id="cat3" class="com.hui.pojo.Cat" />

解决方法:并且id不符合,可以在@Resource中使用参数name指定对应bean

@Resource(name = "cat22")
public void setCat(Cat cat) {
    this.cat = cat;
}

6.4.9、@Autowired 比较 @Resource

  • 可用于自动装配
  • @Autowired 通过byname方式实现,要求对象存在
  • @Resource ,先通过byname实现,找不到在通过byType实现

6.5、使用注解开发

Spring4后,需要保证aop的包导入,不然不支持context约束

导入注解的支持:(初始化)

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

6.5.1、组件注解

详见手册,1.9.8

  1. 使用 @Component 标注类为组件,使用@Value设置值
package com.hui.dao;

import org.springframework.stereotype.Component;
// 等于<bean id="user" class="com.hui.pojo.User" />
// id默认为类名小写

@Component
public class User {
    @Value("Hui3c");
    public String name;
}
  1. 在配置文件(配置文件名规范为:applicationContext.xml)中导入
<!--自动加载com.hui.dao下所有的模板-->
<context:component-scan base-package="com.hui.dao" />
  1. 运行
public class Text31 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // Beanid 默认为类的小写
        User user = context.getBean("user", User.class);
        System.out.println(user.name);
    }
}

@Component 组件在web开发中有几个衍生的注解,类似alias,功能都一样

  • 在dao层使用 @Respsitory
  • 在service层使用 @Service
  • 在controller 使用 @Controller

6.5.2、在开发中的建议

  • xml:更加万能,适用于任何场景,维护简单方便

  • 注解:不是自己类使用不了,维护复杂

  • xml用来管理bean

  • 注解只负责完成属性的注入

其他问题(坑)/技巧

找不到配置文件

找不到配置文件,或者资源文件夹被手贱删除

class path resource [.xml] cannot be opened because it does not exist

解决方法

设置资源文件夹,手动指定beans文件

打开Project Structure后,指定beans文件

自动补齐返回类型

单元测试框架 junit

<!-- pom.xml 中加入包 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

下篇

下篇

使用java方式配置Spring

注意:这里可以混个眼熟,方便后面分析Spring-Boot源码

  • 不使用Spring的xml配置,由Java来做
  • JavaConfig 是Sping 的一个子项目,在Spring4后,变成核心功能
  • 部分大公司会采用此方案,需要知道这也是Spring配置中的一种
  • String-Boot 中 都是使用该方案,本篇作用为:Spring还有这种用法

1.1、使用纯Java类来做

手册1.12(.1)

  • 方法1:在配置类中定义一个方法,并使用@Bean注解声明

  • 方法2:在User类上使用@Component注解,并在配置类上声明@ComponentScan("User类的路径"),这样会自动扫描@Component并生成Bean

实体类:

使用到了@Component@Value

package com.hui.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 注解:此类被Spring接管,注册进容器中
// 无需此注解器也可以实现堆@Bean的装填
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    // 赋值
    @Value("hui3c")
    public void setName(String name) {
        this.name = name;
    }

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

配置类:

  • @Configuration定义为配置文件
  • @ComponentScan 扫描包下的类
  • @Import 引入其他配置类
  • @Bean 注册Bean,具体的方法为
    • id 为类的方法的名字, class 为方法的返回值
package com.hui.config;

import com.hui.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// 定义为配置文件,该类也会被Spring 容器托管
@Configuration

// 扫描包
@ComponentScan("com.hui.pojo")

// 引入其他配置类
@Import(HuiConfig2.class)

public class HuiConfig {

    // 注册bean,相当<bean id="方法名" class="函数返回值">
    // <bean id="getUser" class="User">
    // @Bean 直接返回自己new的对象,不加@Component 也可获得
    @Bean
    public User getUser() {
        // class
        return new User();
    }
}

测试类:

使用new AnnotationConfigApplicationContext( ) 读取配置类,导入对象必须为class

import com.hui.config.HuiConfig;
import com.hui.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Upcon {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(HuiConfig.class);
        User getUser = context.getBean("getUser",User.class);
        System.out.println(getUser.getName());
    }
}

代理模式

代理模式是SpringAOP的底层(SpringAOP 和 SpringMVC)

分类:静态代理动态代理

2.1、静态代理

角色:

  • 抽象角色:使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理的真实角色,代理真实角色,会做以卸附属操作
  • 客户:被代理对象的人(注意是被动)

代理模式的好处:

  • 使真实角色的操作更加存粹!不用关注公共业务
  • 公共交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的适合,方便集中管理!

缺点(静态代理):

  • 真实角色就会产生一个代理角色,代码翻倍(解决方法:动态代理)

房东(真实角色):只有出租房的需求 LandLord.class`

package com.hui.demo;

public class LandLord implements Rent{

    // 真实角色
    @Override
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}

代理商(代理角色):除了出租房需求,还有附属的操作:签合同,带你看房 Proxy.class

package com.hui.demo;

// 代理角色也要租房子,
public class Proxy implements Rent {

    private LandLord landLord;

    public Proxy() {
    }

    public Proxy(LandLord landLord) {
        this.landLord = landLord;
    }

    // 代理要做的事情
    public void rent() {
        // 通知房东即将出售
        landLord.rent();
        // 代理做的事情:签合同
        hetong();
        // 代理做的事情:看房
        seeHouse();
    }

    // 以下都是代理要做的事情
    public void  seeHouse() {
        System.out.println("看房");
    }

    public void hetong() {
        System.out.println("签署合同");
    }
}

“出租房”这个动作,用接口来表示 Rent.interface

package com.hui.demo;

// 接口:租房
public interface Rent {
    public void rent();
}

你(客户):除了租到房还能享受到其他的”特殊待遇”(比如中介费啥的) Client.class

package com.hui.demo;

public class Client {
    public static void main(String[] args) {

        // 房东
        LandLord landLord = new LandLord();
        landLord.rent();

        // 通过代理来租房子
        Proxy proxy = new Proxy(landLord);
        proxy.rent();
    }
}

2.2、AOP(静态代理)

抽象角色:

package com.hui.demo2;

public interface UserService {
    // 抽象角色:增删改查
    public void add();
    public void del();
    public void update();
    public void query();
}

业务实现接口类:

package com.hui.demo2;

// 实现Userservice接口,做一个真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了!");
    }
    public void del() {
        System.out.println("减少了!");
    }
    public void update() {
        System.out.println("更新了!");
    }
    public void query() {
        System.out.println("查询了!");
    }
}

静态代理类:

package com.hui.demo2;

// 也需要去实现抽象类
public class UserServiceProxy implements UserService{

    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }
    //... 省略其他三个
    public void log(String msg) {
        System.out.println("使用了" + msg + "方法");
    }
}

客户使用:

package com.hui.demo2;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        // 代理谁
        proxy.setUserService(userService);
            
        // 由代理访问
        proxy.add();
    }

2.3、动态代理(反射)

如果不知道反射是啥,可以查看 Java-注解与反射

  • 动态代理于静态代理角色一样
  • 动态代理类是动态生成
  • 分类:基于interface的动态代理,基于class的动态代理
    • 基于接口:JDK动态代理
    • 基于类:cglib
    • java 字节码实现:javasist
  • 静态代理有的好处动态代理都有,除此之外:
    • 一个动态代理可以代理多个类

两个常见类:Proxy:代理 ,InvocationHandler:调用处理程序

接口类:

package com.hui.demo4;

public interface UserService {
    // 抽象角色:增删改查
    public void add();
    public void del();
    public void update();
    public void query();
}

实现Userservice接口,(被代理类):

package com.hui.demo4;

// 实现Userservice接口,做一个真实对象
public class UserServiceImpl implements UserService {
    public UserServiceImpl() {
    }

    public void add() {
        System.out.println("增加了!");
    }
    public void del() {
        System.out.println("减少了!");
    }
    public void update() {
        System.out.println("更新了!");
    }
    public void query() {
        System.out.println("查询了!");
    }

}

(通过这个生成代理类)万用模板:

package com.hui.demo4;

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

// 使用这个类自动生成代理
public class ProxyInvocationHandler implements InvocationHandler {
    // 被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成得到代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 反射获得类名
        log(method.getName());
        // 使用反射执行
        Object res = method.invoke(target, args);
        return res;
    }
    
    // 可以附带日志功能
    public void log(String msg) {
        System.out.println("执行了"+msg+"方法");
    }
}

客户端类:

package com.hui.demo4;

public class Client {
    public static void main(String[] args) {
        // 1. 实例化真实角色
        UserServiceImpl userService = new UserServiceImpl();

        // 2.代理角色: 现在没有,通过程序来创建
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        // 3.调用程序处理角色来处理我们要调用的接口对象
        pih.setTarget(userService);

        // proxy 动态生成,没有写代理类,手动强制为要代理的类
        UserService proxy = (UserService)pih.getProxy();
        proxy.add();
    }
}

AOP

3.1、什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

名词:

  • 横切关注点:我们需要关注的部分就是关注点。如日志、安全、事务…
  • 切面(Aspect):关注点被切成很多个对象。指
  • 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作,有类似beforeafter 之类的信息,指类中的一个方法
  • 目标对象(Target):被多个切面,通知的对象。即,被通知的对象、被代理的对象。
  • 切入点(Pointcut):要拦截的的方法,通过切入点表达式拦截add,search。切面通知。
  • 连接点(Join point):与切入点匹配的执行点,SpringAOP中总是表示一个方法的执行。与切入点匹配的执行点。
  • 织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。

导入aspectjweaver包(织入包)

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
</dependencies>

3.2、Spring的AOP接口实现

  1. MethodBeforeAdvice:前置通知实现
  2. AfterReturningAdvice:后置通知实现
  3. ThrowsAdvice:异常通知实现
  4. MethodInterceptor:环绕通知实现

方式一: Spring 的AOP接口(该方法可以使用反射的属性,理解较难,可用性高)

有一接口对应实现类:

package com.hui.service;

public interface UserService {
    void add();
    void delete();
}
// -------------------------

package com.hui.service;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }
    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }
}

有两切面:

  • 他们各自实现MethodBeforeAdviceAfterReturningAdvice方法
package com.hui.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

// 前置,
public class Log implements MethodBeforeAdvice {
    // method 要执行目标对象的方法
    // args 参数
    // target 目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass() + "的" + method.getName() + "方法被执行了");
    }
}

// ----------------------
package com.hui.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

// 后置  ,需要去实现spring中的 AfterReturningAdvice 接口
public class AfterLog implements AfterReturningAdvice {
  
     // method 要执行目标对象的方法
    // args 参数
    // target 目标对象
    // returnValue 返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        
        // 载里面可以使用反射,非常灵活
        System.out.println("执行了"+method.getName()+"方法,返回结果是"+returnValue);
    }
}

导入bean:

  • 可能需要了解 execution 表达式的知识
<?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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 导入对象 -->
    <bean id="userService" class="com.hui.service.UserServiceImpl" />
    <bean id="log" class="com.hui.log.Log" />
    <bean id="afterLog" class="com.hui.log.AfterLog" />

    <!-- 导入 aop 约束-->
    <aop:config>
        <!-- 在哪里执行: 切入点 , expression: 表达式 execution(要执行的位置) -->
        <!-- execution 要执行的位置! (*(修饰词) *(返回值) *(类名) *(方法名) *(参数)) -->
        <!-- com.hui.service.UserServiceImpl.*(..) 下的所有类下的所有方法,(..):表示所有参数 -->
        <aop:pointcut id="pointcut" expression="execution(* com.hui.service.UserServiceImpl.*(..) )"/>

        <!-- 执行环绕:把afterLog类切入到pointcut方法上 -->
         <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        <!-- 执行环绕:把log类切入到pointcut方法上 -->
         <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

测试类:

public class Aop317 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理代理的是接口,不是实现类
        UserService userService = context.getBean("userService", UserService.class);
        
        userService.add();
    }
}

/*
输出:
class com.hui.service.UserServiceImpl的add方法被执行了
增加了一个用户
执行了add方法,返回结果是null
*/

3.3、自定义类实现

自定义类方法实现简单,容易理解,没有反射灵活

实现类和接口、测试类 使用上面的。然后创建一个diy自定义类:

package com.hui.diy;

public class Diy {
    public void before() {
        System.out.println("====== before 方法被执行了 ======");
    }

    public void after() {
        System.out.println("====== 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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 导入对象 -->
    <bean id="diyPlay" class="com.hui.diy.Diy" />
    <bean id="userService" class="com.hui.service.UserServiceImpl" />
    <!-- 导入 aop 约束-->
    <aop:config>
        <!--自定义切面, ref 要引用的类-->
        <aop:aspect ref="diyPlay">
            <!-- 切入点 -->
            <!-- execution 要执行的位置! (*(修饰词) *(返回值) *(类名) *(方法名) *(参数)) -->
            <aop:pointcut id="point" expression="execution(* com.hui.service.UserServiceImpl.*(..))"/>

            <!--通知-->
            <aop:before method="before" pointcut-ref="point" />
            <aop:after method="after" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>
</beans>

3.4、通过注解实现

真实开发中常用,但每次写注解都需要重复写execution表达式

注解指示器 通知
@Before 调用之前执行
@After 返回或异常后调用
@AfterReturning 返回后调用
@AfterThrowing 抛出异常后调用
@Around 即那个目标方法封装

新增一个AnnotationPoint类:(测试类和被代理类不动)

  • 如果使用环绕的话,截断后需要使用proceed() 用于执行方法
  • 类:com.hui.diy.AnnotationPoint
// 标注此类为一个切面
@Aspect
public class AnnotationPoint {
    
    @Before("execution(* com.hui.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("=== before 方法被执行 ===");
    }

    @After("execution(* com.hui.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("=== after方法被执行 ===");
    }

    // 环绕增强:给个参数:要处理切入的点。不常用
    @Around("execution(* com.hui.service.UserServiceImpl.*(..))")
    public void aroud(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前!");

        // 执行方法,它还有更多的方法
        // 如果不执行,就只会输出“环绕前! 环绕后!”
        Object proceed = jp.proceed();
        System.out.println("环绕后!");
    }
}

托管对象

<?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: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/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.hui.service.UserServiceImpl" />
    <bean id="annotationPoint" class="com.hui.diy.AnnotationPoint" />
    <!--开启注解支持 自动代理  -->
    <aop:aspectj-autoproxy />
</beans>

输出结果:

环绕前!
=== before 方法被执行 ===
增加了一个用户
=== after方法被执行 ===
环绕后!

Spring-Mybatis

如果不知道Mybatis是什么,可以查看 Java-Mybatis 学习笔记

4.1、回忆Mybatis

  1. 编写实体类
  2. 编写配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试类编写

4.2、初始化环境搭建-回顾

<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>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.4</version>
    </dependency>
    <!-- Spring 操作数据库需要使用Spring-jdbc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
        </dependency>
</dependencies>

数据库使用Mybatis

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user` (
    `id` INT(20) PRIMARY KEY,
    `name` VARCHAR(30) DEFAULT NULL,
    `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `user`(`id`, `name`, `pwd`) VALUES
(1, 'hui', '123456'),
(2, 'hui2', '123456'),
(3, 'hui3', '123456')
  • com.hui.pojo.User 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}
  • 核心配置resources->Mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!-- 核心配置文件 -->
<configuration>
    <!-- 配置返回类的别名 -->
   <typeAliases>
    <typeAlias type="com.hui.pojo.User" alias="user" />
  </typeAliases>
  <environments default="development">
    <!-- 可以配置多套环境 -->
    <environment id="development">
      <!--事务管理-->
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <!-- &amp;表示转义后的“&” 需要设置编码,不然会乱码 -->
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
</configuration>
  • 配置接口类和接口实现配置类
    • com.hui.mapper.UserMapper.interface
    • com.hui.mapper.UserMapper.xml
// 接口类
public interface UserMapper {
    List<User> selectUser();
}
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.mapper.UserMapper">
    <select id="selectUser" resultType="user">
        select * from mybatis.user
    </select>
</mapper>
  • 测试类:
public void test() throws IOException {
    // 这部分在Mybatis 篇是使用工具类来完成的

    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sessionFactory.openSession(true);

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser();

    for (User user : users) {
        System.out.println(user);
    }
}

出现问题

找不到生成后的xxx.xml

pom.xml中配置

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>

        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

4.3、启航

mybatis-Spring中文文档:http://mybatis.org/spring/zh/index.html

  • mybatis无缝整合到Spring中
  1. 编写数据源配置
  2. 创建sqlSessionFactory
  3. sqlSessionTemple
  4. 需要给接口加实现类【】
  5. 测试类

创建实现类、接口类

  • 创建实现类com.hui.mapper.UserMapperImpl.java
  • 使用set方法接收一个sqlsession
// ---------------实体类 User.java-------------
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

// -----------UserMapper.interface---------------
public interface UserMapper {
    List<User> selectUser();
}


// ------------UserMapperImpl------------
public class UserMapperImpl implements UserMapper{

    // 该类下的所有操作都使用sqlsession执行
    private SqlSessionTemplate sqlSession;

    // 接收sqlSession 对象
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }
    // ---------------

    // 还是通过sqlSession 来获得到对象,返回
    public List<User> selectUser() {
        return sqlSession.getMapper(UserMapper.class).selectUser();
    }
}

Mybatis托管至Bean

mybatis操作托管到bean中,bean托管的是实现类

创建文件resources->spring-dao.xml

  1. 引入datasource
    • 用于数据库连接
  2. 加载sqlSessionFactory
    • 用于创建sqlSession
  3. 加载sqlSession
  4. 托管对应实现类
<?xml version="1.0" encoding="UTF8"?>
<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
        ">
    <!-- 1. 引入datasource:使用spring数据源替换mybatis的配置
            需要引入spring-jdbc
     -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!-- 2. sqlSessionFactory -->
    <!-- (2)绑定数据源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:com/hui/mapper/*.xml" />
    </bean>

    <!-- 3.sqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--使用构造方法注入-->
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>
    
    <!-- 4. 托管对应实现类 --->
    <bean id="userMapper"  class="com.hui.mapper.UserMapperImpl" >
        <!-- 将sql对象通过构造器传入 -->
        <property name="sqlSession" ref="sqlSession" />
    </bean>
</beans>

核心文件配置

创建文件resources->mybatis-config.xml可以简写

  • 只在这里编写 settingtypeAlias
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
  <!--别名建议还是在原来地方配置-->
  <typeAliases>
    <typeAlias type="com.hui.pojo.User" alias="user" />
  </typeAliases>
<!--
  <settings>
    <setting name="" value=""/>
  </settings>-->
</configuration>

实现类配置

实现配置文件:

  • com.hui.mapper.UserMapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--绑定一个dao/mapper接口-->
<mapper namespace="com.hui.mapper.UserMapper">
    <select id="selectUser" resultType="user">
        select * from mybatis.user
    </select>
</mapper>

测试类

测试类:

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    for (User user : userMapper.selectUser()) {
        System.out.println(user);
    }
}

4.4、或者?可以用另一种方法

  • springSqlSessionSqlSessionTemplate

SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。这样就不需要每次在实现类里面用set方法传入sqlSession对象了

  • 通过继承类来使用sqlSession
  • 继承SqlSessionDaoSupport类,然后使用getSqlSession获得sqlSession
  • 同时需要在bean中指定sqlSessionFactory属性值为sqlSessionFactory
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
    public List<User> selectUser() {
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        return mapper.selectUser();
    }
}

配置文件的简化

spring-dao.xml 用来存放不变的内容,如申请sqlsession

<?xml version="1.0" encoding="UTF8"?>
<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
        ">
    <!-- 1. 引入datasource:使用spring数据源替换mybatis的配置
            需要引入spring-jdbc
     -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!-- 2. sqlSessionFactory -->
    <!-- (2)绑定数据源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:com/hui/mapper/*.xml" />
    </bean>

    <!-- 3.sqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--使用构造方法注入-->
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

    <bean id="userMapper"  class="com.hui.mapper.UserMapperImpl" >
        <!-- 将sql对象通过构造器传入 -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
</beans>

applicationContext.xml

<?xml version="1.0" encoding="UTF8"?>
<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
        ">

    <import resource="spring-dao.xml" />

    <bean id="userMapper"  class="com.hui.mapper.UserMapperImpl" >
        <!-- 将sql对象通过构造器传入 -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
</beans>

事务管理

  • 要么都成功,要么都失败!

事务的ACID

  • 原子性:要么都成功,要么都失败
  • 一致性:要么都被提交,要么都被废弃
  • 隔离性:多个业务操作同一个资源,防止数据损坏
  • 持久性:事务完成,无论系统发生问题,结果不好影响

Spring中事务管理

  • 容器管理事务:AOP横切面方式
  • 编程式事务管理:要修改代码

5.1、编写错误环境模拟事务

  1. 编写接口类:com.hui.mapper.UserMapper
public interface UserMapper {
    int addUser(User user);
    int deleteUser(int id);
}
  1. 编写实现类:UserMapperImpl
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }   
}
  1. 编写实现配置:UserMapper.xml
<insert id="addUser" parameterType="user">
    insert into mybatis.user (id, name, pwd) value (#{id},#{name},#{pwd});
</insert>

<delete id="deleteUser" parameterType="int">
    # 错误在这里 delete !!! s !!!
    deletes from mybatis.user where id=#{id}
</delete>
  1. 测试类:
@Test
public void addAndDel() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    // 插入
    User user = new User(4,"hui4c" ,"23333");
    userMapper.addUser(user);
    userMapper.deleteUser(1);
}

这时数据能插入,但不能删除

5.2、织入事务

spring-dao.xml配置如下:(tx就是事务,需要导入)

  1. 配置声明式事务
  2. 配置事务通知
  3. 织入
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--1. 配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="datasource" />
        <!-- 或 <property name="dataSource" ref="datasource"/>-->
    </bean>
    
    <!--2. 配置事务通知 固定-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务。配置事务的传播特征:new -->
        <tx:attributes>
            <tx:method name="add" />
            <tx:method name="delete"/>
            <tx:method name="update"/>
            <tx:method name="query"/>
           <!-- 或者全部监听 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
  • 每个tx:method 还有propagation:传播属性

    <!-- 默认propagation就是 REQUIRED-->
    <tx:method name="add" propagation="REQUIRED"/>

    Spring中七种Propagation类的事务属性:

    REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
    SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
    MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
    REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
    NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

最常用两个已经加着重

  • 像查询的方法,可以添加read-only,这样就只能被读取
<tx:method name="query" read-only="true" />

一般的配置如下

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" 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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 1. 引入datasource:使用spring数据源替换mybatis的配置
            需要引入spring-jdbc
     -->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!-- 2. sqlSessionFactory -->
    <!-- (2)绑定数据源 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:com/hui/mapper/*.xml" />
    </bean>

    <!-- 3.sqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--使用构造方法注入-->
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

    <bean id="userMapper"  class="com.hui.mapper.UserMapperImpl" >
        <!-- 将sql对象通过构造器传入 -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

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

    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager1">
        <!--给哪些方法配置事务。配置事务的传播特征:new -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <!-- 切入点:mapper下所有方法,所有切入点-->
        <aop:pointcut id="pointCut" expression="execution(* com.hui.mapper.*.*(..))"/>

        <!-- 将事务切入 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut" />
    </aop:config>

</beans>

我查阅了几篇文章,发现并没有提到为什么会失败,但别人都是用这种方法进行事务回滚


文章作者: Hui3c
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-ND 4.0 许可协议。转载请注明来源 Hui3c !
  目录