Spring开发扫盲
Flow

虽然已经进入夏天,但是让我们来学习一下春天!

从头开始看黑马的视频太耗费时间,不要求自己一接触就所有知识点都很熟悉,能了解开发框架的65%就满足。后面遇到不会的知识点再回炉加深印象

所以我选择站在前人的肩膀上学习

全文主要参考:https://drun1baby.top/2022/08/18/Spring%E5%BC%80%E5%8F%91%E5%AD%A6%E4%B9%A0/

有些比较理论的知识点可能会直接复制粘贴,本文为本人学习笔记,可能不具有任何学习参考作用,注意甄别,如果有错误的地方,十分欢迎指正🙏

说是扫盲,之前也接触过Spring框架的漏洞,但是具体实现还是不了解,所以这里先写几个自己的盲点,看写完这篇笔记最后会不会扫盲成功:

  • Spring和SpringBoot,SpringCloud之间的区别是什么
  • 那些听起来高大上的编程思想代称(比如IOC)究竟是什么意思
  • 简单写一个Spring的demo
  • 怎么实现和数据库的连接
  • 平时遇到的Swagger页面是怎么实现的,为什么会泄漏,如何通过修改配置修复未授权访问漏洞

Spring简介

Spring是一个轻量级的Java 开发框架,简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。Spring 的理念:不去重新发明轮子。其核心是控制反转(IOC)和面向切面(AOP)。

组成

Spring作为一个框架,本身有很多功能快,如果我们要实现功能,可以直接通过配置框架来完成业务,Spring支持的一些功能如上图

  • 最低下是core,
  • 往上走开始支持IOC,AOP编程思想
  • 支持ORM(数据库处理语句)
  • 也支持很多Web,比如web application 、servlet等

学习路线 Spring -> SpringBoot -> SpringCloud

传统Spring也有痛点

  • 配置繁琐:需要手动配置数据源、事务管理器、MVC 组件等。
  • 依赖冲突:不同模块的依赖版本需要手动协调。
  • 部署复杂:需打包 WAR 并部署到外部服务器。

所以出现了Spring boot,这个后面再接触

因此我引申出了一些疑问,Spring、SpringBoot、SpringCloud这三个之间的区别是什么,AI答道:

  • Spring → 地基(IOC、AOP)。
  • Spring Boot → 快速盖房子的工具(自动配置、内嵌服务器)。
  • Spring Cloud → 盖楼房的整体方案(解决水电、电梯、消防等系统问题)。

概念扫盲

IOC、AOC分别是什么意思

IOC(Inversion of Control 控制反转)

核心思想:别自己动手,让别人帮你安排!

生活例子:点餐 vs 自己做饭

  • 传统方式(没有IOC):就像你自己去超市买菜、切菜、炒菜,全程自己控制流程。
  • IOC方式:你去餐厅点菜,告诉服务员你要什么(比如“一份宫保鸡丁”),厨房会自动做好端给你。你不需要关心鸡丁怎么切、火候怎么控制,你只关心“吃什么”。

在代码中:

  • 没有IOC:A类要用B类,直接在A类里 new B()(自己动手做)。
  • 有IOC:A类说“我需要一个B”,由IOC容器(比如Spring框架)自动把B的实例“注入”给A。A不用自己创建B,只管用。

好处:代码更灵活!如果你想换一个B的实现类(比如从MySQL换成Oracle数据库),只需要改配置文件,不用改A的代码。


AOC(Aspect Oriented Programming 面向切面编程)

核心思想:别到处复制粘贴重复代码!

生活例子:安检

  • 假设你进地铁、进机场、进商场都要安检。传统做法是每个入口都放一台安检机(重复代码)。
  • AOP的做法是:把安检逻辑抽成一个“切面”,自动在所有入口前插入安检步骤。

在代码中:

  • 没有AOP:每个方法里都要写日志、权限检查、事务处理代码,重复又臃肿。
  • 有AOP:把这些公共逻辑(比如日志)写成一个“切面”,然后告诉框架:“在所有Service方法执行前自动打日志”。代码变得干净!

如何实现?

  • 动态代理:框架会在运行时“偷偷”在你的代码前后插入逻辑(比如日志),但你写的代码里完全看不到这些重复内容。

好处:解耦!修改日志逻辑时,只需改切面代码,所有用到的地方自动生效。

关于这两个概念,详细可以参考这篇文章 https://javaguide.cn/system-design/framework/spring/ioc-and-aop.html

分布式系统

定义

多个计算机(节点)通过网络协同工作,对外表现为一个整体系统的技术架构。

​核心特点​​:

  • 资源分散:数据、计算任务分布在不同的机器上。
  • 透明性:用户感觉不到背后是多台机器在协作。
  • 容错性:部分机器故障时,系统仍能运行(比如你的微信消息不会因为某台服务器宕机而丢失)。

技术场景

  • 分布式数据库:数据分片存储在不同服务器(如MySQL分库分表)。
  • 分布式计算:Hadoop 将大数据任务拆分到多台机器处理。
  • 分布式缓存:Redis 集群缓存数据,分摊压力。

微服务

定义

一种架构风格,将大型应用拆分为多个独立的小服务,每个服务专注一个业务功能,独立开发、部署、扩展。
​核心特点​​:

  • 单一职责:每个服务只做一件事(比如用户服务只管注册登录)。
  • 独立部署:修改用户服务后,无需重新部署整个系统。
  • 技术异构:不同服务可以用不同技术栈(比如订单服务用Java,支付服务用Go)。

技术场景

  • 电商系统拆分为微服务:
    • 用户服务、订单服务、库存服务、支付服务各自独立。
    • 服务之间通过 REST API 或消息队列(如RabbitMQ)通信。

好了现在概念扫盲以后不能说不知道这几个是什么了

下面进入实践环节

Spring核心之一–IOC

传统思想实现

传统的编程思想:Controller 层写接口,去调用 Service 层,Service 层里面有一个 Service 接口,还有一个 ServiceImpl 的实现类,具体的业务是写在 ServiceImpl 里面的。Service 层去调用 Dao 层,也就是我们的实体类,我们的 Dao 层有一个 Dao 的抽象接口,还有一个 DaoImpl 的实现类。

大致流程就是 ControllerServiceDao

先写Dao层

1
2
3
4
5
package traditional.Dao;

public interface UserDAO {
public void getUser();
}
1
2
3
4
5
6
7
8
9
10
package traditional.Dao;

import traditional.Dao.UserDAO;

public class UserDAOImpl implements UserDAO {
@Override
public void getUser() {
System.out.println("输出获取用户数据");
}
}

Service层

1
2
3
4
5
package traditional.Service;

public interface UserService {
void getUser();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package traditional.Service;

import traditional.Dao.UserDAO;
import traditional.Dao.UserDAOImpl;

public class UserServiceImpl implements UserService {
private UserDAO userDAO = new UserDAOImpl();

@Override
public void getUser() {
userDAO.getUser();
}
}

然后来一个test文件去调用

1
2
3
4
5
6
7
8
9
10
11
12
package traditional;


import traditional.Service.UserService;
import traditional.Service.UserServiceImpl;

public class TestApplication {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}

new的是service,调试跟进会跟到最里面的DAO层

运行结果,也可以在图片里看到我的文件结构

IOC处理情景

如果现在来了一个新需求,要我们用MySql获取到User,我们应该把UserServiceImpl作修改

1
2
3
private UserDAO userDAO = new UserDAOImpl();
修改成
private UserDAO userDAO = new 对应的 DAOImpl 类

看起来只是修改一个地方,但是本质上还是会修改到DAO层,现在只有一个getUser(),如果方法多了,来个getName(),geyAge()…修改起来就很麻烦了。

所以引入了IOC思想,我们在UserServiceImpl.Java里面引入一个setter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package traditional.Service;

import traditional.Dao.UserDAO;
import traditional.Dao.UserDAOImpl;

public class UserServiceImpl implements UserService {
private UserDAO userDAO;

public void getUserDAO(UserDAO userDAO){
this.userDAO = userDAO;
}

@Override
public void getUser() {
userDAO.getUser();
}
}

对应的TestApplication.java改一个调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package traditional;


import traditional.Dao.MysqlUserDAOImpl;
import traditional.Service.UserService;
import traditional.Service.UserServiceImpl;

public class TestApplication {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.getUserDAO(new MysqlUserDAOImpl());
userService.getUser();
}
}

也要新建对应的MysqlUserDAOImpl接口

1
2
3
4
5
6
7
8
package traditional.Dao;

public class MysqlUserDAOImpl implements UserDAO{
@Override
public void getUser() {
System.out.println("通过 MySQL 的方式获取 User");
}
}

现在再运行就是打印

1
通过 MySQL 的方式获取 User

相比原来传统的做法,UserServiceImpl自己控制了依赖(UserDAO)怎么来的。现在的思路就是多一个setter方法,

  • UserServiceImpl没有自己创建UserDAO对象,而是通过外部传入(注入)
  • 控制权从UserServiceImpl自己new,变成了外部传入,所以这就是控制反转(IOC)

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

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

Spring通过xml装配

写一个hello类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Hello {  
private String name;

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

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

再来一个bean.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="traditional.Dao.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>

然后运行test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import traditional.Dao.Hello;

public class test {
@Test
public void testHelloBean() {
// 解析beans.xml文件,生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// getBean: 参数即为spring配置文件中bean的id
Hello hello = (Hello) context.getBean("hello");
hello.show();

// 验证name属性已被正确注入
System.out.println("Name property: " + hello.getName());
}
}

执行结果

我的理解:代码解析beans.xml,获得里面对应的bean对象,下一句context从那么多bean里面(假设有很多)拿到那个叫”hello”的获取对象,而不是像传统那样自己的new,下一句正常调用Hello里面的方法

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

利用IOC的思想装配

新写一个beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="mysqlImpl" class="traditional.Dao.MysqlUserDAOImpl"/>
<bean id="oracleImpl" class="traditional.Dao.OracleUserDAOImpl"/>

<bean id="UserServiceImpl" class="traditional.Service.UserServiceImpl">
<!--ref引用spring中已经创建很好的对象-->
<!--value是一个具体的值,基本数据类型-->
<property name="userDAO" ref="oracleImpl"/>
</bean>
</beans>

对应的test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import traditional.Dao.Hello;
import traditional.Service.UserServiceImpl;

public class test {
@Test
public void testHelloBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 需要什么就直接get什么!
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
}

如果要换成用mysql方式获取,修改ref成这样即可

1
<property name="userDAO" ref="mysqlImpl"/>

小结:

  • 所有类都要到xml里面装配
  • 所有bean用容器获取
  • 容器里面取得的 bean,拿出来就是一个对象,用对象调用方法即可

IOC创建对象

利用无参构造方法

写一个user类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package dao;

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 );
}
}

在beans.xml里面增加

1
2
3
4
<!--     User bean配置-->
<bean id="user" class="dao.User">
<property name="name" value="flow"/>
</bean>

最后test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import dao.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) context.getBean("user");
//调用对象的方法
user.show();
}
}

运行结果

利用有参构造方法

改一下user类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package dao;

public class User {
private String name;

public User(String name){
this.name = name;
System.out.println("利用有参方式构造");
}
public String getName() {
return name;
}

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

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

beans.xml有三种方法编写

下标赋值

1
2
3
4
<bean id="user" class="dao.User"> 
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="flow"/>
</bean>

类型赋值(不推荐使用)

1
2
3
<bean id="user" class="dao.User">
<constructor-arg type="java.lang.String" value="flow"/>
</bean>

直接用参数名

1
2
3
4
<bean id="user" class="dao.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="flow"/>
</bean>

如果test里面只有一句

1
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

运行也会打印出”利用有参方式构造”。

所以在配置文件加载的时候。其中管理的对象都已经初始化了(并且是单例模式,取到的对象是全局唯一)

拿出两个User对象,只会打印一句”利用有参方式构造”

1
2
3
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
system.out.println(user == user2) //结果为true

Spring配置

这部分内容比较理论,没什么实际,都是直接摘自大师傅的博客的

封装思想:sprin有一个applicationContext.xml来统领配置

比如现在有三个程序员写了代码,分别把程序打包到a.xml,b.xml,c.xml

这时候用applicationContext.xml来统领配置,把上面这三个xml都放在一个里面,就符合封装思想。

alias别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名

1
2
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>

我们要调用的时候可以调用 name,也可以调用别名。

Bean的配置

  • id:bean 的唯一标识符,也就是相当于我们学的对象名
  • class:bean 对象所对应的会限定名:包名+类型
  • name:也是别名,而且 name 可以同时取多个别名
1
2
3
4
5
<bean id="user" class="dao.User" name="u1 u2,u3;u4">

<property name="name" value="chen"/>

</bean>

import

import 一般用于团队开发使用,它可以将多个配置文件,导入合并为一个

用到封装思想

a.xml b.xml c.xml > applicationContext.xml

1
<import resource="a.xml"/>  <import resource="b.xml"/>  <import resource="c.xm1"/>

使用的时候,直接使用总的配置就可以了

有重名的情况出现时,按照在总的xml中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入xml中的那个

DI依赖注入

构造注入

对应前面IOC的实现场景

set注入

依赖注入本质就是set注入

  • 依赖 :bean对象的创建依赖于容器
  • 注入:bean对象中的所有对象都靠容器注入

下面做一下实现准备

pojo实体类

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package pojo;

public class Address {
private String address;

public String getAddress() {
return address;
}

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

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package pojo;  

import java.util.*;

public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;

public String getName() {
return name;
}

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

public Address getAddress() {
return address;
}

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

public String[] getBooks() {
return books;
}

public void setBooks(String[] books) {
this.books = books;
}

public List<String> getHobbies() {
return hobbies;
}

public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}

public Map<String, String> getCard() {
return card;
}

public void setCard(Map<String, String> card) {
this.card = card;
}

public Set<String> getGames() {
return games;
}

public void setGames(Set<String> games) {
this.games = games;
}

public String getWife() {
return wife;
}

public void setWife(String wife) {
this.wife = wife;
}

public Properties getInfo() {
return info;
}

public void setInfo(Properties info) {
this.info = info;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbies=" + hobbies +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}

XML注入文件

beans.xml这么写

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="pojo.Student">
<property name="name" value="flow"/>
</bean>

</beans>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.junit.Test;  
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
Student student = (Student) context.getBean("student");
//调用对象的方法 .
System.out.println(student.getName());
}
}

官方支持多种注入方式,现在都实现一下

bean注入

这里的值是ref

1
2
3
4
5
6
7
<<bean id="addr" class="pojo.Address">  
<property name="address" value="China"/>
</bean>

<bean id="student" class="pojo.Student">
<property name="address" ref="addr"/>
</bean>
数组注入
1
2
3
4
5
6
7
<property name="books">  
<array>
<value>西游记</value>
<value>三国演义</value>
<value>红楼梦</value>
</array>
</property>
List注入
1
2
3
4
5
6
7
<property name="hobbies">  
<list>
<value>read</value>
<value>basketball</value>
<value>music</value>
</list>
</property>
Map注入
1
2
3
4
5
6
7
<!--map键值对注入 -->
<property name="card">
<map>
<entry key="username" value="root" />
<entry key="password" value="root" />
</map>
</property>
set注入
1
2
3
4
5
6
7
8
<!--set(可去重)注入 -->
<property name="games">
<set>
<value>xx</value>
<value>oo</value>
<value>ha</value>
</set>
</property>
空指针 null 注入
1
2
3
4
<!--空指针null注入 -->
<property name="mh">
<null></null>
</property>
properties 常量注入
1
2
3
4
5
6
7
<!--properties常量注入 -->
<property name="info">
<props>
<prop key="id">123456</prop>
<prop key="name">flow</prop>
</props>
</property>

把test类修改成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;


public class TestApplication {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
Student student = (Student) context.getBean("student");
System.out.println(student);
}
}

拓展方式注入

官方文档中这算两种方式注入,c命令与p命名空间注入

P 就代表着 Properties,c 就代表着 Constructor 这个方式可以减轻写配置文件的繁琐程度

准备

新建一个user类 这里没有有参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package pojo;  

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

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

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

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

p命名空间注入:需要在头文件加入约束文件(也可以理解成命名空间)

1
2
3
4
 导入约束 : xmlns:p="http://www.springframework.org/schema/p"

<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="pojo.User" p:name="flow" p:age="20" />

现在beans.xml这么写

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="pojo.Student">
<property name="name" value="flow"/>
</bean>
<bean id="user" class="pojo.User" p:name="flow" p:age="20" />

</beans>

再写一个test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;
import pojo.User;


public class Test2 {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) context.getBean("user");
System.out.println(user);
}
}

打印

1
Student{name='flow', address=null, books=null, hobbies=null, card=null, games=null, wife='null', info=null}
c命名空间注入

现在bean想这么写

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="pojo.Student">
<property name="name" value="flow"/>
</bean>
<bean id="user" class="pojo.User" c:name="flow" c:age="20"/>

</beans>

然后看到爆红

这里需要加上有参构造器,因为c也就是所谓的构造器注入

在User类里面多加一个

1
2
3
4
5
public User(String name, int age){
this.name = name;
this.age = age;
System.out.println("利用有参方式构造");
}

然后运行结果

1
2
利用有参方式构造
Student{name='flow', address=null, books=null, hobbies=null, card=null, games=null, wife='null', info=null}

Bean的作用域

在spring里面,那些组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 bean。

bean 就是由 IoC 容器初始化、装配及管理的对象

几种作用域中,request、session 作用域仅在基于 Web 的应用中使用(不必关心你所采用的是什么 Web 应用框架),只能用在基于 Web 的 Spring ApplicationContext 环境。

单例模式(Spring默认)

默认的形式

1
<bean id="user" class="pojo.User"" c:name="flow" c:age="20" scope="singleton"></bean>

原型模式(Prototype)

每次从容器里get的时候都会获取一个新对象

1
<bean id="user" class="pojo.User"" c:name="flow" c:age="20" scope="prototype"></bean>

其余的 request、session、application 这些只能在 Web 开放中使用

Bean自动装配

说明

配置xml文件的时候手写难免出现拼写错误,大小写混淆等问题,也没办法及时检测,使用自动装配可以优化这个过程,提高效率

  • 自动装配是使用 spring 满足 bean 依赖的一种方法
  • spring 会在应用上下文中为某个 bean 寻找其依赖的 bean。

Spring 中 bean 有三种装配机制,分别是:

  1. 在 xml 中显式配置;
  2. 在 Java 中显式配置;
  3. 隐式的 bean 发现机制和自动装配 **

环境

假设现在有一个人,有猫和兔子,先实现一下手动装配

cat类

1
2
3
4
5
6
7
package pojo;

public class Cat {
public void shout(){
System.out.println("i am a cat!");
}
}

rabbit类

1
2
3
4
5
6
7
package pojo;

public class Rabbit {
public void shout(){
System.out.println("i am a rabbit!");
}
}

people类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package pojo;

public class People {
private Cat cat;
private Rabbit rabbit;
private String name;

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

public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Rabbit getRabbit() {
return rabbit;
}
public void setRabbit(Rabbit rabbit) {
this.rabbit = rabbit;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Beans.xml这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="cat" class="pojo.Cat"/>
<bean id="rabbit" class="pojo.Rabbit"/>

<bean id="people" class="pojo.People">
<property name="name" value="flow"/>
<property name="cat" ref="cat"/>
<property name="rabbit" ref="rabbit"/>
</bean>

</beans>

test类这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.People;


public class Test2 {
@Test
public void TestApplication() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People person = (People) context.getBean("people", People.class);
person.getCat().shout();
person.getRabbit().shout();
}
}

最后输出

使用byName 和 byType 自动装配

都是修改beans.xml即可

byName
  • 去找 beanId 的,这个 Id 必须要是 setter 方法里有的,比如上文代码中,我们只可以 byNamebeanId 是 cat 和 rabbit

把beans.xml修改成

1
2
3
4
5
6
<bean id="cat" class="pojo.Cat"/>
<bean id="rabbit" class="pojo.Rabbit"/>

<bean id="people" class="pojo.People" autowire="byName">
<property name="name" value="flow"/>
</bean>

people里面不用写清cat和rabbit,直接有一个byName获取之前获取的beanId即可

byType
1
2
3
4
5
6
<bean class="pojo.Cat"/>
<bean class="pojo.Rabbit"/>

<bean id="people" class="pojo.People" autowire="byType">
<property name="name" value="flow"/>
</bean>

前面导入cat和rabbit类的写法有点小改变,用class即可

使用注解自动装配

jdk1.5 开始支持注解,Spring2.5 开始全面支持注解。

准备工作:利用注解的方式注入属性。

  • 在 spring 配置文件中引入 context 文件头,这里可以通过添加这一行,代表支持注解的语句,会自动引入文件头。
1
<context:annotation-config/>
使用 @AutoWired 注解进行自动装配

@AutoWired注解可以代替setter方法,现在需要修改一下people类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package pojo;

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

public class People {
@Autowired
private Cat cat;
@Autowired
private Rabbit rabbit;
private String name;

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

public Cat getCat() {
return cat;
}
public Rabbit getRabbit() {
return rabbit;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

再修改beans.xml运行

几个注意点

1
2
3
4
5
6
7
8
9
10
// @Nullable字段标记了这个注解,说明该字段可以为空
public People(@Nullable String name) {
this.name = name;
}

// 说明:false,对象可以为null;true,对象必须存对象,不能为null。
@Autowired(required=false)

// 如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
使用 @AutoWired + @Qualifier

@Autowired不能唯一装配时,需要@Autowired+@Qualifier

如果 @Autowired 自动装配环境比较复杂。自动装配无法通过一个注解完成的时候,可以使用 @Qualifier(value = “xxx”) 去配合 @Autowired 使用,指定一个唯一的 id 对象

如果现在有多个beanId

1
2
3
4
<bean class="pojo.Cat"/>
<bean class="pojo.Rabbit"/>
<bean id="rabbit2" class="pojo.Rabbit"/>
<bean id="cat2" class="pojo.Cat"/>

再执行test类会报错,需要在@AutoWired下面指定@Qualifier

1
2
3
4
5
6
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "rabbit2")
private Rabbit rabbit;

这样才可以成功输出

@Resource

比较灵活,可以理解成byName+byType

  • @Resource 如有指定的 name 属性,先按该属性进行 byName 方式查找装配;
  • 其次再进行默认的 byName 方式进行装配;
  • 如果以上都不成功,则按 byType 的方式自动装配。
  • 都不成功,则报异常。
1
2
3
4
<bean id="rabbit1" class="pojo.Rabbit"/>
<bean id="cat1" class="pojo.Cat"/>
<bean id="rabbit2" class="pojo.Rabbit"/>
<bean id="cat2" class="pojo.Cat"/>

然后在people类添加注解

1
2
3
4
@Resource(name = "cat2")
private Cat cat;
@Resource(name = "rabbit2")
private Rabbit rabbit;

这样也可以用

小结

@AutoWired和@Resource对比

  • 都可以用来装配bean,要写在字段上,或写在setter方法上
  • @AutoWired通过byType方法实现,必须要求这个对象存在【常用】
  • @Resource默认用byName方法实现,如果找不到名字,再用byType,如果都找不到,则会报错【常用】
  • 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired 先 byType,@Resource先 byName

使用注解开发

一些说明

在 Spring4 之后,想要使用注解形式,必须得要引入 aop 的包,这个在 Spring-webmvc 的大包里面基本也是自带的。

我们之前都是使用 bean 的标签进行 bean 注入,但是实际开发中,我们一般都会使用注解!

配置扫描哪些包下面的注解

1
2
<!--指定注解扫描包-->
<context:component-scan base-package="pojo"/>

在制定包下编写类,增加注解

1
2
3
4
5
6
7
8
9
package pojo;

import org.springframework.stereotype.Component;

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "flow";
}

然后来一个测试类,运行成功

属性注入

也就是赋值

可以不用setter方法,直接在名上添加@Value(“值”)

1
2
3
4
5
6
@Component
public class User {
// 相当于<property name="name" value="John"/>
@Value("flow")
public String name;
}

如果有setter方法,在setter方法上添加@Value(“值”)

1
2
3
4
5
6
7
8
9
10
@Component
public class User {
public String name;

// 也可以放在set方法上面
@Value("flow")
public void setName(String name) {
this.name = name;
}
}

这个 @Value 的注解在 Spring-mvc 的项目会经常用到,而里面的这个值可以写在 application.properties 中,到时候就可以直接调用。

衍生的注解

有这些注解,其实就是替代了在配置文件里面的配置步骤,可以更方便快捷

@Component 三个衍生注解:

为了更好的进行分层,Spring 可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web 层
  • @Service:service 层
  • @Repository:dao 层

写上这些注解,就相当于将这个类交给 Spring 管理装配了!学习 SpringBoot 的时候这种思想就更为明显。

作用域

@scope

  • singleton:默认的,Spring 会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

小结

xml 与注解:

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

最佳实践:

  • xml 用来管理 Bean
  • 注解只用来完成属性的注入
  • 可以不用扫描,扫描是为了类上的注解,但是要开启注解支持 <context:annotation-config/>

用java的方式配置Spring

spring本身写xml太繁琐,现在尝试用java写config,提高代码可读性,全程不用xml文件

User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package pojo;

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

@Component
public class User {
private String name;

public String getName() {
return name;
}
// 属性注入值
@Value("flow")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

MyConfig类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import pojo.User;

@Configuration
@ComponentScan("pojo")
public class MyConfig {
@Bean
public User getUser(){
return new User();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.People;
import pojo.User;
import config.MyConfig;


public class Test2 {
@Test
public void TestApplication() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User user = (User) applicationContext.getBean("getUser");
System.out.println(user.getName());
}
}

然后成功运行输入flow

结合mybatis的实际开发

基础开发

过一下这个流程,这个还不是很熟悉

pom.xml里面导入包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<dependencies>  
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>

User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package pojo;

public class User {
private int id;
private String name;
private String pwd;

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

编写核心配置文件 mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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>
<package name="pojo"/>
</typeAliases>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.65.5:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<mappers>
<package name="mapper"/>
</mappers>
</configuration>

编写接口UserMapper

1
2
3
4
5
6
7
package mapper;
import pojo.User;
import java.util.List;

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

接口对应的xml文件,UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.UserMapper">

<select id="selectUser" resultType="User">
select * from test.user </select>

</mapper>

最后是一个测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import mapper.UserMapper;
import pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
@Test
public void selectUser() throws IOException {

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

List<User> userList = mapper.selectUser();
for (User user: userList){
System.out.println(user);
}

sqlSession.close();
}

}

成功运行查出数据

Mybatis-Spring

前面用的是传统方法连接数据库,下面看spring里面设计mybatis的是怎么操作的

基础知识

在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring 框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

下面的内容比较理论,直接摘抄

关于 SqlSessionFactory

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

1
2
3
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 <settings><typeAliases>元素。

关于 SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

1
2
3
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoImpl implements UserDao {

private SqlSession sqlSession;

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

public User getUser(String userId) {
return sqlSession.getMapper...;
}
}

按下面这样,注入 SqlSessionTemplate:

1
2
3
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>

上述都是官方文档的做法,接下来根据我们自己的业务需求,进行整合。

整合实现一

首先创建一个applicationContext.xml

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

在里面把数据源替换成mybatis的

1
2
3
4
5
6
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.65.5:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

配置SqlSessionFactory,关联mybatis

1
2
3
4
5
6
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

注册 sqlSessionTemplate,关联 sqlSessionFactory

1
2
3
4
5
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

增加DAO接口实现类,私有化sqlSessionTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package dao;

import mapper.UserMapper;
import org.mybatis.spring.SqlSessionTemplate;
import pojo.User;

import java.util.List;

public class UserMapperImpl implements UserMapper {

//sqlSession不用我们自己创建了,Spring来管理
private SqlSessionTemplate sqlSession;

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

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

}

注册bean实现

1
2
3
<bean id="userMapper" class="com.example.dao.userMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>

测试类

1
2
3
4
5
6
7
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> user = mapper.selectUser();
System.out.println(user);
}

运行效果

整合实现二

第二个方式让dao继承support类,直接利用 getSqlSession() 获得,然后直接注入 SqlSessionFactory . 比起方式一 , 不需要管理 SqlSessionTemplate , 而且对事务的支持更加友好

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,就像下面这样:

1
2
3
4
5
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getUser(String userId) {
return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}

一般在这个类里,通常倾向于使用 MapperFactoryBean,因为不需要额外的代码。但是如果DAO里面要做其他不是mybatis的工作或者实现一个非抽象的类,那么这个类就很有用了。

SqlSessionDaoSupport 需要通过属性设置一个 sqlSessionFactorySqlSessionTemplate。如果两个属性都被设置了,那么 SqlSessionFactory 将被忽略。

假设类 UserMapperImplSqlSessionDaoSupport 的子类,可以编写如下的 Spring 配置来执行设置:

1
2
3
<bean id="userDao" class="dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试实现

修改UserMapperImpl类

1
2
3
4
5
6
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}

修改bean配置,把userMapper相关的改成

1
2
3
<bean id="userMapper" class="mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试类

1
2
3
4
5
6
7
8
@Test
public void test() throws IOException{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}

可以正常打印,第二个整合方式和dao没什么关系,经过尝试,如果后面要加新功能,可以考虑多一个service层

梳理了一下整个流程

  1. Spring 容器启动时:
  • 读取 applicationContext.xml

  • 创建数据源(DataSource)

  • 创建 SqlSessionFactory

  • 创建 SqlSession

  • 初始化各个 Mapper 实现类

  1. 执行数据库操作时:
  • 调用 Mapper 接口方法

  • SqlSession 执行对应的 SQL 语句

  • 返回结果

最后发现xml文件精简成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.65.5:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

<bean id="userMapper" class="mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
</beans>

声明式事务

事务概念

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  • 原子性(atomicity)

事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

  • 一致性(consistency)

一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

  • 隔离性(isolation)

可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

  • 持久性(durability)

事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

没有事务管理的测试

在userMapper接口增加三个新的接口

1
2
3
4
5
6
7
//添加一个用户
int addUser(User user);

//根据id删除用户
int deleteUser(int id);

public List<User> test();

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

//增加一些操作
public List<>User test() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
User user = new User(5,"aaa","123456");
mapper.addUser(user);
mapper.deleteUser(5);
return mapper.selectUser();
}

public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}

//新增
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//删除
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}

}

在UserMapper.xml文件添加映射,这里故意写错delete语法

1
2
3
4
5
6
7
8
9
<insert id="addUser" resultType="User">
insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>

<delete id="deleteUser" resultType="User">
deletes from mybatis.user where id=#{id}
</delete>

<select id="test" resultType="User"/>

测试

1
2
3
4
5
6
7
8
9
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> userlist = mapper.test();
for (User user : userlist) {
System.out.println(user);
}
}

会报错sqldelete语句有错误,但是查看数据库,插入内容成功

没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!

以前我们都需要自己手动管理事务,十分麻烦!

但是Spring给我们提供了事务管理,我们只需要配置即可;

Spring事务管理

Spring在不同的事务管理API上定义了一个抽象层,开发人员不用了解底层的事务管理API就可以使用Spring的事务管理,Spring支持编程式事务管理和声明式的事务管理

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用spring事务管理,头文件要添加tx约束

1
2
3
4
xmlns:tx="http://www.springframework.org/schema/tx"

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

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

配置好事务管理器后我们需要去配置事务的通知

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

配置AOP

1
2
3
4
5
<!--配置aop事务-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.example.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

最后完整的xml配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://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">

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.65.5:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>

<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.example.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>

再运行一次上面的测试类,这次delete依然报错,但是也不会插入数据成功

最后

拖拖拉拉写了好久才跟着大师傅的博客写完,囫囵吞枣,理解的不是很深入。拖的太久心累了,先这样告一段落吧

最近todo:

这个视频内容刷完,再回顾一下知识画一个图出来,脑子里再回滚知识点才能吸收好

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep