Spring SpringBoot(详解)

1. Spring简介

1.1 Spring 核心设计思想

1.1.1 Spring 是什么?

  • Spring 是包含了众多⼯具⽅法的 IoC 容器。
  • Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的应⽤程序开发起来更简单。
1.1.1.1 什么是IoC容器?
  • 容器是⽤来容纳某种物品的装置。
  • IoC = Inversion of Control 翻译成中⽂是“控制反转”的意思,控制权发⽣的反转,不再是上级对象创建并控制下级对象了,⽽是下级对象把注⼊将当前对象中,下级的控制权不再由上级类控制了,这样即使下级类发⽣任何改变,当前类都是不受影响的,这就是 IoC 的实现思想。
  • Spring具备两个核心功能:将对象存⼊到容器,从容器中取出对象。对象的创建和销毁的权利都交给 Spring 来管理了,它本身⼜具备了存储对象和获取对象的能⼒。
1.1.1.2 DI 概念说明

DI 是 Dependency Injection 的缩写,翻译成中⽂是“依赖注⼊”的意思。依赖注⼊就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。所以,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦,IoC 是“⽬标”也是⼀种思想,⽽⽬标和思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI就属于具体的实现。

1.1.1.3 IoC容器和普通程序开发的区别
  • 将对象存储在 IoC 容器相当于将以后可能⽤的所有⼯具制作好都放到仓库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。
  • new 对象的⽅式相当于,每次需要工具了才现做,用完扔掉了也不会保存,下次再用的时候还得重新做。

1.2 Spring 创建和使用

在这里插入图片描述

1.2.1 创建 Spring 项目

使⽤ Maven ⽅式来创建⼀个 Spring 项⽬,创建 Spring 项⽬和 Servlet 类似。

  1. 创建⼀个 Maven 项⽬
    在这里插入图片描述
    然后跳转到了这个页面:
    在这里插入图片描述
  2. 添加 Spring 框架⽀持
    在项⽬的 pom.xml 中添加 Spring 框架的⽀持spring-context(spring 上下⽂)和spring-beans(管理对 象的模块),xml 配置如下:
<dependencies>
   <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-context</artifactId> 
        <version>5.2.3.RELEASE</version> 
    </dependency>

    <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-beans</artifactId> 
        <version>5.2.3.RELEASE</version> 
    </dependency>
</dependencies>
  1. 添加启动类
    最后在创建好的项⽬ java ⽂件夹下创建⼀个启动类,包含 main ⽅法即可:
public class App {
    public static void main(String[] args) {

    }
}

到此为止,一个spring项目就搭建好了。

1.2.2 存储 Bean 对象

Bean 就是 Java 语⾔中的⼀个普通对象,例如:

public class User {
   public String sayHi(String name) { 
       return name + " hello!";
   }
}
  1. 创建 Bean
public class User {
    public String sayHi(String name) { 
        return name + " hello!";
    }
}
  1. 将 Bean 注册到容器
    <<1>>在创建好的项⽬中添加 Spring 配置⽂件 spring-config.xml,将此⽂件放到 resources 的根⽬录下, 如下图所示:
    在这里插入图片描述
    <<2>> spring-config.xml 配置⽂件的固定格式为以下内容,复制粘贴即可
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

<<3>> 将 User 对象注册到 Spring 中就可以,在 标签中添加配置 ,格式如下:

<beans>
  <bean id="user" class="com.bit.User"></bean>
</beans>

1.2.3 获取 Bean 对象

  1. 创建 Spring 上下⽂对象
    <<1>>使⽤ ApplicationContext

ApplicationContext context = new ClassPathXmlApplicationContext("spring-con fig.xml");

<<2>>使⽤ BeanFactory


BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

ApplicationContext VS BeanFactory

  • 继承关系和功能⽅⾯:Spring 容器有两个顶级的接⼝:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性, 还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持。
  • 性能⽅⾯:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽ BeanFactory 是需要那个才去加载那个,因此更加轻量。
    <<3>>使用ClassPathXmlApplicationContext
    ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,通过 xml 的配置来获取所有的 Bean 容器。
  1. 获取指定的 Bean 对象
// 1.得到 Spring 上下⽂对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-con fig.xml");
// 2.加载某个 bean
User user = (User) context.getBean("user");

Bean 的 Id 要⼀⼀对应,如下图所示:
在这里插入图片描述

getBean ⽅法的更多⽤法

  • 根据类型获取 Bean:
UserController user = context.getBean(UserController.class);
  • 名称 + 类型获取 Bean:
UserController user = context.getBean("user", UserController.class);

当有⼀个类型被重复注册到 spring-config.xml 中时,只能使⽤根据名称获取了,⽐如以 下场景就会导致程序报错:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2.4 使用 Bean

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下⽂对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml"); 
        // 2.加载某个 bean
        User user = (User) context.getBean("user");
        // 3.调⽤相应的⽅法
        System.out.println(user.sayHi("Java"));
    }
}

1.2.4 使用注解读取和存储对象

  1. 配置扫描路径

配置⼀下存储对象的扫描包路径,只有在被配置的包下且添加了注解的类才能被正确的识别并保存到 Spring 中。

在 spring-config.xml 添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:content="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
 
 <content:component-scan base-package="com.bit.service"></content:component-scan>
 
</beans>

在这里插入图片描述

  1. 添加注解存储 Bean 对象
    将对象存储在 Spring 中,有两种注解类型可以实现:
  • 类注解:@Controller(表示业务逻辑层)、@Service(服务层)、@Repository(持久层)、@Component、@Configuration(配置层)。
  • ⽅法注解:@Bean
    类注解是添加到某个类上的,⽅法注解是放到某个⽅法上的。

Bean 命名规则:
通常 bean 使⽤的都是标准的⼤驼峰命名,读取的时候⾸字⺟⼩写:
在这里插入图片描述
当⾸字⺟和第⼆个字⺟都是⼤写时,不能正常读取到 bean :
在这里插入图片描述
Spring中 bean 存储时⽣成的命名规则:
<1> 在 Idea 中使⽤搜索关键字“beanName”可以看到以下内容:
在这里插入图片描述
在这里插入图片描述
它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,源码如下:

public static String decapitalize(String name) {
   if (name == null || name.length() == 0) {
       return name;
   }
   // 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了 
   if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && 
       Character.isUpperCase(name.charAt(0))){
       return name;
   }
	// 否则就将⾸字⺟⼩写
   char chars[] = name.toCharArray();
   chars[0] = Character.toLowerCase(chars[0]);
   return new String(chars);
}

所以对于上⾯报错的代码,只要改为以下代码就可以正常运⾏了:
在这里插入图片描述

类注解使用示例:

  • @Controller(控制器存储)
    使⽤ @Controller 存储 bean:
@Controller // 将对象存储到 Spring 中
public class UserController {
	public void sayHi(String name) {
		System.out.println("Hi," + name);
	}
}

先使⽤之前读取对象的⽅式读取上⾯的 UserController 对象:

public class Application {
	public static void main(String[] args) {
		// 1.得到 spring 上下⽂
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到 bean
		UserController userController = (UserController) context.getBean("userController");
		// 3.调⽤ bean ⽅法
		userController.sayHi("Bit");
	}
}
  • @Service(服务存储)
    使⽤ @Service 存储 bean
@Service
public class UserService {
	 public void sayHi(String name) {
		System.out.println("Hi," + name);
	 }
}

读取 bean

class App {
	public static void main(String[] args) {
		 // 1.得到 spring 上下⽂
		 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		 // 2.得到 bean
		 UserService userService = (UserService) context.getBean("userService");
		 // 3.调⽤ bean ⽅法
		 userService.sayHi("Bit");
	}
}
  • @Repository(仓库存储)
    使⽤ @Repository 存储 bean
@Repository
public class UserRepository {
	 public void sayHi(String name) {
		System.out.println("Hi," + name);
	 }
}

读取 bean

class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下⽂
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserRepository userRepository = (UserRepository) context.getBean("userRepository");
		// 3.调⽤ bean ⽅法
		userRepository.sayHi("Bit");
	}
}
  • @Component(组件存储)
    使⽤ @Component 存储 bean
@Component
public class UserComponent {
	public void sayHi(String name) {
		System.out.println("Hi," + name);
	}
}

读取 bean

class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下⽂
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserComponent userComponent = (UserComponent) context.getBean("userComponent");
	 	// 3.调⽤ bean ⽅法
		userComponent.sayHi("Bit");
	 }
}
  • @Configuration(配置存储)
@Configuration
public class UserConfiguration {
	public void sayHi(String name) {
		System.out.println("Hi," + name);
	}
}

读取 bean

class App {
	public static void main(String[] args) {
		// 1.得到 spring 上下⽂
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 2.得到某个 bean
		UserConfiguration userConfiguration = (UserConfiguration) context.getBean("userConfiguration");
		// 3.调⽤ bean ⽅法
		userConfiguration.sayHi("Bit");
	 }
}

为什么有这么多功能一样的注解:
它们的功能都是一样的,让程序员看到类注解之后,就能直接了解当前类的⽤途。
程序的⼯程分层,调⽤流程:
在这里插入图片描述
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现,这些注解⾥⾯都有⼀个注解 @Component,说明它们本身就是属于 @Component 的“⼦类”。
在这里插入图片描述

⽅法注解@Bean 使用示例:

⽅法注解要配合类注解使⽤
在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中

  • 使用@Bean存储 bean

@Component
public class Users {

   @Bean
   public User user1() { 
       User user = new User(); 
       user.setId(1); 
       user.setName("Java"); 
       return user;
   }
}

读取bean

public class Application {
   public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       User user = (User) context.getBean("user1"); 
       System.out.println(user.toString());
   }
}

在这里插入图片描述

  • 重命名 Bean
    可以通过设置 name 属性给 Bean 对象进⾏重命名。重
@Component
public class Users { 
   @Bean(name = {"u1"}) 
   public User user1() { 
       User user = new User(); 
       user.setId(1); 
       user.setName("Java"); 
       return user;
   }
}

命名的 name 其实是⼀个数组,⼀个 bean 可以有多个名字。

@Bean(name = {"u1", "us1"}) public User user1() { 
 User user = new User(); 
 user.setId(1); 
 user.setName("Java"); 
 return user;
}

name={} 可以省略

@Bean({"u1", "us1"})
public User user1() { 
  User user = new User(); 
  user.setId(1); 
  user.setName("Java"); 
  return user;
}

此时使⽤ u1/us1 就可以获取到 User 对象了

class App {
   public static void main(String[] args) {
       // 1.得到 spring 上下⽂
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       // 2.得到某个 bean
       User user = (User) context.getBean("u1");
       // 3.调⽤ bean ⽅法
       System.out.println(user);
   }
}
  1. 对象装配

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。

<1> 属性注⼊

属性注⼊使⽤ @Autowired 实现。
属性注⼊的优点是简洁,使⽤⽅便;缺点是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只 有在使⽤的时候才会出现 NPE(空指针异常)。

示例:

Service 类的实现代码如下:

import org.springframework.stereotype.Service;

@Service
public class UserService {

   /**
    * 根据 ID 获取⽤户数据
    *
    * @param id
   * @return
    */
   public User getUser(Integer id) { 
       // 伪代码,不连接数据库 
       User user = new User(); 
       user.setId(id); 
       user.setName("Java-" + id); 
       return user;
   }
}
  • Controller 类的实现代码如下:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller;

@Controller
public class UserController {

   // 注⼊⽅法1:属性注⼊ 
   @Autowired
   private UserService userService;

   public User getUser(Integer id) { 
       return userService.getUser(id); 
   }
}

核⼼实现:
在这里插入图片描述
获取 Controller 中的 getUser ⽅法:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserControllerTest {
   public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); 
       UserController userController = context.getBean(UserController.cla ss);
   		System.out.println(userController.getUser(1).toString());
   }
}

运行结果:
在这里插入图片描述

<2>构造⽅法注⼊

构造⽅法注⼊是在类的构造⽅法中实现注⼊。
构造⽅法注⼊是 Spring 推荐的注⼊⽅式,它的缺点是如果有多个注⼊会显得⽐较臃肿,但出现这 种情况应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式,优点是通⽤性,在使⽤之前⼀定能把保证注⼊的类不为空。

示例:

@Controller
public class UserController2 {

   // 注⼊⽅法2:构造⽅法注⼊ 
   private UserService userService;     // 创建userService引用

   @Autowired 
   public UserController2(UserService userService) {   // 注入并通过构造方法让上面的引用指向注入进来的这个对象
       this.userService = userService;
  }

   public User getUser(Integer id) { 
       return userService.getUser(id); 
   }
}

<<1>> 如果只有⼀个构造⽅法,那么 @Autowired 注解可以省略
在这里插入图片描述
<<2>>如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法,否则 程序会报错
在这里插入图片描述

<3> Setter 注⼊

Setter 注⼊和 构造方法注入 实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解。
Setter ⽅式是 Spring 前期版本推荐的注⼊⽅式,但通⽤性不如构造⽅法,所有 Spring 现版本已 经推荐使⽤构造⽅法注⼊的⽅式来进⾏类注⼊了。

示例:


@Controller
public class UserController3 {

   // 注⼊⽅法3:Setter注⼊ 
   private UserService userService;

   @Autowired
   public void setUserService(UserService userService) { 
       this.userService = userService;
   }

   public User getUser(Integer id) { 
       return userService.getUser(id); 
   }
}

<4> @Resource注入

@Autowired 和 @Resource 的区别:

  • @Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解
  • 相⽐于 @Autowired ,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
  • @Autowired 可⽤于 Setter 注⼊、构造方法注⼊和 属性注⼊。⽽ @Resource 只能⽤于 Setter 注⼊和 属性注⼊ 不能⽤于构造函数注⼊。

<5>使用同⼀类型多个 Bean 报错处理,当出现以下多个 Bean,返回同⼀对象类型时程序会报错。

示例:

@Component
public class Users {
    
   // 第一个bean
   @Bean
   public User user1() { 
       User user = new User(); 
       user.setId(1); 
       user.setName("Java"); 
       return user;
   }

   // 第二个bean
   @Bean
   public User user2() { 
       User user = new User(); 
       user.setId(2); 
       user.setName("MySQL"); 
       return user;
   }
}

在另⼀个类中获取 User 对象:

@Controller
public class UserController4 {

   // 注⼊ 
   @Resource 
   private User user;

   public User getUser() { 
       return user;
   }
}

执⾏结果:
在这里插入图片描述
解决方法:
<<1>>使⽤ @Resource(name=“XXX”)来指定bean

@Controller
class UserController4 { 
   // 注⼊
   @Resource(name = "user1") 
   private User user;

   public User getUser() { 
       return user;
   }
}

<2> 使⽤ @Qualifier来指定bean

@Controller
public class UserController5 { 
    // 注⼊
    @Autowired 
    @Qualifier(value = "user2") 
    private User user;

    public User getUser() { 
        return user;
    }
}

2. Bean 详解

2.1 Bean的作用域

限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式。
Bean 有 6 种作⽤域:singleton(单例作⽤域),prototype(原型作⽤域/多例作⽤域), request(请求作⽤域),session(回话作⽤域),application(全局作⽤域),websocket(HTTP WebSocket 作⽤域)。后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种。

2.1.1 singleton

Spring默认选择该作⽤域,该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired等注⼊)都是同⼀个对象。通常⽆状态的Bean(Bean对象的属性状态不需要更新)使⽤该作⽤域,单例可以很⼤程度上提⾼性能。
示例:
公共bean

@Component
public class Users {
 	@Bean
 	public User user1() {
 		User user = new User();
 		user.setId(1);
 		user.setName("Java"); // 【重点:名称是 Java】
 		return user;
 	}
}

A ⽤户使⽤时,进⾏了修改操作:

@Controller
public class BeanScopesController {
	 @Autowired
 	 private User user1;
	 public User getUser1() {
		 User user = user1;
		 System.out.println("Bean 原 Name:" + user.getName());
		 user.setName("悟空"); // 【重点:进⾏了修改操作】
		 return user;
	 }
}

B ⽤户再去使⽤公共 Bean :

@Controller
public class BeanScopesController2 {
	 @Autowired
	 private User user1;
	 public User getUser1() {
		 User user = user1;
		 return user;
	 }
}

查看 A ⽤户和 B ⽤户公共 Bean 的值:

public class BeanScopesTest {
 public static void main(String[] args) {
	 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
 
	 BeanScopesController beanScopesController = context.getBean(BeanScopesController.class);
	 System.out.println("A 对象修改之后 Name:" + beanScopesController.getUser1().toString());
 
	 BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class);
	 System.out.println("B 对象读取到的 Name:" + beanScopesController2.getUser1().toString());
 }
}

执⾏结果:
在这里插入图片描述

2.1.2 prototype

每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的对象实例。通常有状态(Bean对象的属性状态需要更新)的Bean使⽤该作⽤域。

2.1.3 request

每次http请求会创建新的Bean实例,类似于prototype。用于⼀次http的请求和响应的共享Bean,限定SpringMVC中使⽤。

2.1.4 session

在⼀个http session中,定义⼀个Bean实例,用于用户回话的共享Bean,⽐如:记录⼀个⽤户的登陆信息。限定SpringMVC中使⽤。

2.1.5 application

在⼀个http servlet Context中,定义⼀个Bean实例,用于Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息。限定SpringMVC中使⽤。

application 是 Spring Web 中的作⽤域,作⽤于 Servlet 容器。singleton 是 Spring Core 的作⽤域,singleton 作⽤于 IoC 的容器。

2.1.6 websocket

在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例,用于WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。限定Spring WebSocket中使⽤。

2.2 设置Bean的作用域

使⽤ @Scope 标签就可以⽤来声明 Bean 的作⽤域。
<1> 直接设置值:@Scope(“prototype”)
<2> 使⽤枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

示例;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class Users {
	 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	 @Bean(name = "u1")
	 public User user1() {
		 User user = new User();
		 user.setId(1);
		 user.setName("Java"); // 【重点:名称是 Java】
		 return user;
	 }
}

2.2 Bean的生命周期

2.2.1 Spring 执行流程

在这里插入图片描述

2.2.2 Bean 的执行流程

启动 Spring 容器 --> 实例化 Bean(分配内存空间,从⽆到有) --> Bean注册到Spring中(存操作) --> 将Bean装配到需要的类中(取操作)。

2.2.3 Bean的生命周期

⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。

在这里插入图片描述

  1. 实例化 Bean(为 Bean 分配内存空间)
  2. 设置属性(Bean 注⼊和装配)
  3. Bean 初始化
    3.1 实现了各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware ApplicationContextAware >的接⼝⽅法;
    3.2 执⾏ BeanPostProcessor 初始化前置⽅法;
    3.3 执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏;
    3.4 执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);
    3.5 执⾏ BeanPostProcessor 初始化后置⽅法。
  4. 使⽤ Bean
  5. 销毁 Bean。执行销毁容器的各种⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method。
  • 实例化和初始化的区别:
    实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者 提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理。
  • 如何理解Bean生命周期:
    Bean 的⽣命流程看似繁琐,但咱们可以以⽣活中的场景来理解它,⽐如我们现在需要买⼀栋房⼦,那么我们的流程是这样的:
  1. 先买房(实例化,从⽆到有);
  2. 装修(设置属性);
  3. 买家电,如洗⾐机、冰箱、电视、空调等([各种]初始化);
  4. ⼊住(使⽤ Bean);
  5. 卖出去(Bean 销毁)。

3. Spring AOP

3.1 什么是 Spring AOP?

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,通过预编译和运行期间动态代理来实现程序功能的统一维护的一种技术,它是对某⼀类事情的集中处理。Spring AOP 是⼀个框架,是⼀种对 AOP 思想的实现,它们的关系和IoC 与 DI 类似。

面向对象编程的缺陷:

面向对象的特点是继承、多态和封装。封装就要求将功能分散到不同的对象中去称为职责分配,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了,好处是降低了代码的复杂程度,使类可重用。在分散代码的同时,也增加了代码的重复性,比如说,我们在两个类中,可能都需要在每个方法中做日志,按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容:
在这里插入图片描述
也可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是这样一来,这两个类跟这个独立的类就有耦合了,它的改变会影响这两个类。
在这里插入图片描述

如何理解AOP的好处?

某天小明一觉醒来,发现自己光着腚穿越到三亿年前的非洲大草原,为了填饱肚子他只能天天靠捕猎一些小动物生存下去,考虑到卫生问题,需要弄熟了再吃,他把猎物先放到一边开始接着进行钻木取火,终于火苗着了,小明开始烤肉了,饱餐了一顿。每次钻木取火都很费劲,小明总是累得满头大汗才得到一点点小火星,但是没办法,原始社会只能这样。

<小明每天都要钻木取火>:
在这里插入图片描述
假如是另外一种情况:同样是某一天,小明一觉醒来发现自己穿越到了三亿年前的非洲大草原上,万幸的是他一摸兜里还揣着打火机,但是他还得打猎要不然就得饿肚子,和上次不一样的是,他不用再费劲巴拉的钻木取火了,直接“啪”一点,打火机就能冒出火苗,直接就能点火烤肉了,然后他美美地饱餐了一顿。每次需要用到火时,拿出打火机啪一点就行了。
在这里插入图片描述

类比小明的故事,当我们在做后台系统时,如果没有AOP,所有需要判断用户登录的页面中的方法都要各自实现或调用用户验证的方法,而且用户登录验证都是的相同方法,当功能越多时,要写的登录验证也就越多,代码会更臃肿,修改和维护成本也会升高。
有了AOP之后,只需要在某一处配置一下,所有需要判断用户登录页面中的方法就可以全部实现用户登录验证了,不需要每个方法中都写相同的用户登录验证了。
AOP是OOP的补充,OOP从纵上区分出一个个的类来,而AOP则从横向上向对象中加入特定的代码,使OOP由原来的二维变为三维了,由平面变成立体了。

AOP的用途:

统一日志记录,统一方法执行时间统计,统一的返回格式设置,统一的异常处理,事务的开启和提交等

3.2 AOP相关概念

在这里插入图片描述

3.2.1 Aspect

Aspect 是 包含了 Advice ,Pointcut,和 Aspect 的类,相当于AOP实现的某个功能的集合。

3.2.2 Join Point

程序执行过程中插入 Aspect 的一个点,这个点可以是方法调用时、抛出异常时、甚至修改字段时,Aspect 能够利用这些点插入到程序的正常流程中并添加新的行为。

3.2.3 Pointcut

Pointcut 提供了一组规则(这个规则使用 AspectJ pointcut expression language 描述)来匹配Join Point,给满足规则的 Join Point 添加 Advce。Pointcut 相当于保存了众多 Join Point 的一个集合,如果把 Poincut 看成一个表,而 Join Point 就是表中的一条条数据。

3.2.4 Advice

Aspect 的工作被称为 Advice。
Advice 定义了 Aspect 是什么、何时使用,描述了 Aspect 要完成的工作,还决定合适执行这个工作。

在Spring Aspect 类中,可以在方法上使用注解,使其成为 Advice 方法,在满足条件后会通知这个方法执行。

  • 前置通知:@Before,Advice方法会在目标方法调用之前执行。
  • 后置通知:@After,Advice方法会在目标方法返回或抛出异常后调用。
  • 返回之后再通知:@AfterReturning,Advice方法会在目标方法返回后调用。
  • 抛异常后通知:@AfterThrowing,Advice方法会在目标方法抛出异常后调用。
  • 环绕通知:@Aroud,Advice方法包裹了目标方法,Advice方法会在目标方法调用之前和调用之后执行。

3.3 实现Spring AOP

3.1.1 添加 AOP 框架支持

在 pom.xml 中添加如下配置:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.1.2 定义 Aspect 和 Pointcut

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect // 表明此类为⼀个切⾯
@Component
public class UserAspect {
 	// 定义切点,这⾥使⽤ AspectJ 表达式语法
 	@Pointcut("execution(*com.example.demo.controller.UserController.*(..))")
 	public void pointcut(){ } // 不需要有方法体,只是起到一个标识左右,标识下面的 Advice 方法具体指的是哪个Pointcut
 
}

切点表达式:
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
AspectJ ⽀持的三种通配符:

    • :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
  • … :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤
    • :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的
      所有⼦类包括本身
      在这里插入图片描述
      表达式示例:

execution(* com.cad.demo.User.*(..)) :匹配 User 类⾥的所有⽅法。
execution(* com.cad.demo.User+.*(..)) :匹配该类的⼦类包括该类的所有⽅法。
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有⽅法。
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法。 execution(* >addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个 参数类型是 int

具体实现:

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class UserAspect {

    // 定义切点⽅法
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){ }

    // 前置通知
    @Before("pointcut()")
    public void doBefore(){ 
        System.out.println("执⾏ Before ⽅法"); 
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter(){ 
        System.out.println("执⾏ After ⽅法"); 
    }

    // return 之前通知 
    @AfterReturning("pointcut()")
    public void doAfterReturning(){ 
        System.out.println("执⾏ AfterReturning ⽅法"); 
    }

    // 抛出异常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){ 
        System.out.println("执⾏ doAfterThrowing ⽅法"); 
    }

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){ 
        Object obj = null; 
        System.out.println("Around ⽅法开始执⾏"); 
        try {
            // 执⾏拦截⽅法
           obj = joinPoint.proceed();
        } catch (Throwable throwable) { 
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏"); 
        return obj;
    }
}

具体案例:

3.3 Spring AOP 的实现原理:

在这里插入图片描述

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截。
Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。
默认情况下,实现了接⼝的类,使 ⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。

3.3.1 代理的⽣成时机:织⼊(Weaving)

织⼊是把Aspect应⽤到⽬标对象并创建新的代理对象的过程,Aspect在指定的 Join Point 被织⼊到⽬标对 象中。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就 是以这种⽅式织 Aspect 的
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊Aspect。
  • 运⾏期:Aspect在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊ Aspect 时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊Aspect 的。
3.3.1.1 动态代理

此种实现在设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动态的织⼊字节码。
Spring 框架中的AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代 理⽬标都是被代理类中的⽅法,在运⾏期动态的织⼊字节码⽣成代理类。
CGLIB是Java中的动态代理框架,主要作⽤就是根据⽬标类和⽅法,动态⽣成代理类。
Java中的动态代理框架,⼏乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
字节码框架是直接操作 class 字节码的框架。可以加载已有的class字节码⽂件信息,修改部
分信息,或动态⽣成⼀个 class。

3.3.1.1.1 JDK 动态代理实现

JDK 实现时,先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。

import org.example.demo.service.AliPayService; 
import org.example.demo.service.PayService;

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

//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被
代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {

    //⽬标对象即就是被代理对象 
    private Object target;

    public PayServiceJDKInvocationHandler( Object target) { 
        this.target = target;
    }

    //proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throw s Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");

        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);

        //4.时间统计结束 
        System.out.println("记录结束时间"); 
        return retVal;
    }

    public static void main(String[] args) {

        PayService target=  new AliPayService(); 
        //⽅法调⽤处理器
        InvocationHandler handler =
            new PayServiceJDKInvocationHandler(target); 
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建 
        PayService proxy = (PayService) Proxy.newProxyInstance(
                        target.getClass().getClassLoader(), 
                new Class[]{PayService.class}, 
                handler
        );
        proxy.pay();
    }
}
3.3.1.1.2 CGLIB 动态代理实现
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor; 
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;

import java.lang.reflect.Method;

public class PayServiceCGLIBInterceptor implements MethodInterceptor {

    //被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target){ 
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, Method Proxy methodProxy) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");

        //通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);

        //4.时间统计结束 
        System.out.println("记录结束时间"); 
        return retVal;
    }

    public static void main(String[] args) {
        PayService target=  new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),n ew PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}
3.3.1.1.3 JDK 和 CGLIB 实现的区别
  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏
    时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代 理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完 成。
  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类
    对象。

4. Spring 事务

4.1 什么是事务?

将⼀组操作封装成⼀个执⾏单元,要么全部成功要么全部失败。

  • 为什么要⽤事务?
    ⽐如转账分为两个操作:第⼀步操作:A 账户 -100 元,第⼆步操作:B 账户 +100 元。如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么 A 账户平⽩⽆故的 100 元就“⼈间蒸发”了。如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。

4.1.1 事务特性

事务有4 ⼤特性(ACID):原⼦性(Atomicity,或称不可分割性)、持久性(Consistency)
、⼀致性(Durability)和隔离性(Isolation,⼜称独⽴性)。

  • 原⼦性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中
    间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个
    事务从来没有执⾏过⼀样。
  • ⼀致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写⼊的资料必须完
    全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯
    作。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 隔离性:数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务
    并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read
    uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

4.2 Spring 事务的实现

MySQL 中的事务使⽤:
事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务。

-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;

4.2.1 Spirng 编程式事务

步骤:

  1. 开启事务(获取事务)
  2. 提交事务
  3. 回滚事务

SpringBoot 内置了两个对象:DataSourceTransactionManager ⽤来开启事务、提交或回滚事务,TransactionDefinition 是事务的属性,在开启事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus。
示例:

@RestController
public class UserController {
	 @Resource
 	private UserService userService;
	 // JDBC 事务管理器
	 @Resource
	 private DataSourceTransactionManager dataSourceTransactionManager;
	 // 定义事务属性
	 @Resource
	 private TransactionDefinition transactionDefinition;
   	@RequestMapping("/sava")
	 public Object save(User user) {
		 // 开启事务
		 TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
 		// 插⼊数据库
 		int result = userService.save(user);
 		// 提交事务
 		dataSourceTransactionManager.commit(transactionStatus);
		// // 回滚事务
		// dataSourceTransactionManager.rollback(transactionStatus);
		 return result;
	 }
}

4.2.2 Spring 声明式事务

在需要的⽅法上添加 @Transactional 注解即可,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务。
示例:

@RequestMapping("/save")
@Transactional
public Object save(User user) {
	int result = userService.save(user);
 	return result;
}

示例:使⽤以下代码,分别设置 @Transactional 注解和不设置 @Transactional

在这里插入图片描述

4.2.2.1 @Transactional 作⽤范围

@Transactional 可以⽤来修饰⽅法或类:

  • 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法
  • 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效
4.2.2.2 @Transactional 参数

在这里插入图片描述

4.2.2.3 @Transactional 在异常被捕获不会进⾏事务⾃动回滚

示例:

@RestController
public class UserController { 
    @Resource
    private UserService userService;

    @RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        // 插⼊数据库
        int result = userService.save(user); 
        try {
            // 执⾏了异常代码(0不能做除数) 
            int i = 10 / 0;
        } catch (Exception e) { 
            System.out.println(e.getMessage()); 
        }
        return result;
    }
}
4.2.2.3.1 解决方案1:将异常重新抛出

示例:

@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE) public Object save(User user) {
    // 插⼊数据库
    int result = userService.save(user);
    try {
        // 执⾏了异常代码(0不能做除数)
        int i = 10 / 0;
    } catch (Exception e) { 
        System.out.println(e.getMessage()); 
        // 将异常重新抛出去
        throw e;
    }
    return result;
}
4.2.2.3.2 解决方案2:⼿动回滚事务

在⽅法中使⽤TransactionAspectSupport.currentTransactionStatus() 可 以得到当前的事务,然后设置回滚⽅法 setRollbackOnly 就可以实现回滚了

@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
    // 插⼊数据库
    int result = userService.save(user);
    try {
        // 执⾏了异常代码(0不能做除数)
        int i = 10 / 0;
    } catch (Exception e) {
        System.out.println(e.getMessage());
        // ⼿动回滚事务 
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnl y();
    }
    return result;
}
4.2.2.4 @Transactional 工作原理

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默 认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。 @Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途 遇到的异常,则回滚事务。

在这里插入图片描述
在这里插入图片描述

4.3 Spring 事务的隔离级别

  • 事务的隔离级别:数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务 并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化 (Serializable)。
    在这里插入图片描述

4.3.1 Spring 事务的隔离级别

  • MySQL 事务隔离级别:
  1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,⽽未提交的数据可能会发⽣回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
  2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间 的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
  3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询的结果⼀致。但也会有新的问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败 (因为唯⼀约束的原因)。明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这就叫幻读 (Phantom Read)。
  4. SERIALIZABLE:序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突,从⽽解决
    了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多。在这里插入图片描述
  • 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致 第⼀个事务读取的数据是错误的。
  • 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修 改了。
  • 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。
  • 查询MySQL事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;
      // 全局事务隔离级别     // 当前连接的事务隔离级别

执行结果:
在这里插入图片描述

4.3.1.1 Spring 中设置事务隔离级别
  • Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  • Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  • Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  • Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  • Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。
4.3.1.2 Spring 中事务隔离级别的设置

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置。
在这里插入图片描述
示例:

@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE) public Object save(User user) {
    // 业务实现
}

4.4 Spring 事务的传播机制

4.4.1 事务传播机制是什么?

Spring 事务传播机制定义了多个包含了事务的⽅法,相互调⽤时,事务是如何在这些⽅法间进⾏传递的。

4.4.2 事务传播机制的作用

事务传播机制是保证⼀个事务在多 个调⽤⽅法间的可控性的(稳定性的)。在这里插入图片描述

4.4.3 Spring 事务传播机制

  • Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
  • Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  • Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
  • Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  • Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
  • Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  • Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
    在这里插入图片描述
    以情侣关系为例来理解:

在这里插入图片描述

4.4.4 Spring 事务传播机制使用示例

4.4.4.1 REQUIRED

6. SpringBoot

Spring 的诞⽣是为了简化 Java 程序的开发的,⽽ Spring Boot 是为了快速开发 Spring 程序开发而诞生的。

Spring Boot 的优点:

  • 快速集成框架,Spring Boot 提供了启动添加依赖的功能,⽤于秒级集成各种框架。
  • 内置运⾏容器,⽆需配置 Tomcat 等 Web 容器,直接运⾏和部署程序
  • 快速部署项⽬,⽆需外部容器即可启动并运⾏项⽬。
  • 可以完全抛弃繁琐的 XML,使⽤注解和配置的⽅式进⾏开发。
  • ⽀持更多的监控的指标,可以更好的了解项⽬的运⾏情况。

6.1 SpringBoot 创建和使用演示

6.1.1 创建

  1. 安装Spirng Boot Helper插件,安装好之后它的名字就变成Spirng Initialzr and Assistant 了
    在这里插入图片描述在这里插入图片描述
  2. 创建 Spring Boot 项目

首先把 Maven 配置为国内源:
第⼀次打开 Spring Boot 项⽬需要加载很久,因为当前 Spring Boot 框架并没有在⾃⼰的本地仓库,为了加速 Spring Boot 项⽬的下载,在打开项⽬之前,先把 Maven 配置为国内源。
<1> 打开 settings
在这里插入图片描述
<2> 在这里插入图片描述
在这里插入图片描述
检查 User Settings file 的 settings.xml ⽂件是否存在,如果不存在,复制下⾯配置了国内源的 settings.xml ⽂件,放到 User Settings file ⽬录下。

// 配置了国内源的 settings.xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="htt p://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven. apache.org/xsd/settings-1.1.0.xsd"> 
<localRepository>C:\Users\intel\.m2\repository</localRepository> 
 <mirrors>
   <mirror>
     <id>alimaven</id>
     <name>aliyun maven</name> 
     <url>http://maven.aliyun.com/nexus/content/groups/public/</url> 
     <mirrorOf>central</mirrorOf>
   </mirror>
 </mirrors>
</settings>

如果存在,检查 settings.xml 是否配置了国内源,对照代码修改配置即可。
<3>删除本地存储 jar ⽬录中的所有⽂件
在这里插入图片描述
<4> 切换到 Idea 中,重新下载 jar 包:
在这里插入图片描述
如果出现中⽂会导致下载了 jar 包,但是在项⽬中不能正常使⽤;如果还是下载失败那就是本地⽹速问题,尝试更好⽹络,使⽤⼿机热点或朋友的⼿机热点尝试,如果还是不⾏,间隔 4 ⼩时之后再试。

正式开始创建项⽬:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述点击 Finish 就完成 Spring Boot 的项⽬创建了。

网页版创建:
不使⽤ Idea 也可以创建 Spring Boot 项⽬,我们可以使⽤ Spring 官⽅提供的⽹⻚版来创建Spring Boot 项⽬。
<1> 先访问:https://start.spring.io在这里插入图片描述在这里插入图片描述
点击⽣成按钮会下载⼀个 Spring Boot 的 zip 包,解压 zip 之后⽬录如下:
在这里插入图片描述
<2> 使⽤ Idea 打开之后,Spring Boot 项⽬就算创建成功了
在这里插入图片描述

6.1.1 使用

Spring项目的特点:约定大于配置。

  • 约定把要注入到容器的类和启动类放到同级目录下,此时Spring Boot 项目才能将bean注入到容器中,类上标注 @SpringBootApplication 就可以启动 Spring Boot 项⽬了。
    在这里插入图片描述
    对⽐ Spring 的项⽬我们也可以看到这⼀特点,⽐如在 Spring 中也是要配置 Bean 的扫描路径的,⽽ Spring Boot 则不需要,Spring 配置如下:在这里插入图片描述

项目目录介绍

  • src/main/java 为 Java 源代码
  • src/main/resources 为静态资源或配置⽂件,/static:静态资源⽂件夹;/templates:模版资源⽂件夹。在这里插入图片描述
  1. 运行项目
    点击启动类的 main ⽅法就可以运⾏ Spring Boot 项⽬了,
    在这里插入图片描述
    启动成功如下图所示:
    在这里插入图片描述

  2. 输出 Hello world
    之前 Spring 是⼀个普通 Java 项⽬,没 办法直接和浏览器进⾏互动,⽤ Spring Boot 可以直接实现和浏览器及⽤户的交互。
    <1> 在创建的项⽬包路径下创建 UserController ⽂件,实现代码如下:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

@RestController 
@RequestMapping("/user") 
public class UserController {
    @RequestMapping("/sayhi") 
    public String sayHi(){ 
        return  "Hi,Spring Boot."; 
    }
}

<2> 重新启动项⽬,访问 http://localhost:8080/user/sayhi 最终效果如下:
在这里插入图片描述

6.2 SpringBoot 热部署

  1. 添加热部署框架⽀持
    在 pom.xml 中添加如下框架引⽤:
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
 <scope>runtime</scope>
</dependency>
  1. Settings 开启项⽬⾃动编译
    在这里插入图片描述在这里插入图片描述

  2. 开启运⾏中热部署
    在这里插入图片描述
    在这里插入图片描述

⾼版本 Idea 设置(IntelliJ IDEA 2021.2 之后版本)在这里插入图片描述

  1. 使⽤ Debug 启动(⾮Run)

**在这里插入图片描述**

6.3 SpringBoot 配置文件

6.3.1 配置文件作用

整个项⽬中所有重要的数据都是在配置⽂件中配置的:

  • 数据库的连接信息(包含⽤户名和密码的设置),
  • 项⽬的启动端⼝
  • 第三⽅系统的调⽤秘钥等信息
  • ⽤于发现和定位问题的普通⽇志和异常⽇志等

如果没有配置信息,那么 Spring Boot 项⽬就不能连接和操作数据库,甚⾄是不能保存可以⽤ 于排查问题的关键⽇志,所以配置⽂件⾮常重要。

6.3.2 配置⽂件的格式

在这里插入图片描述

  • 理论上讲 properties 可以和 yml ⼀起存在于⼀个项⽬当中,当 properties 和 yml ⼀起存在⼀个项⽬中时,如果配置⽂件中出现了同样的配置,⽐如 properties 和 yml 中都配置“server.port”, 那么这个时候会以 properties 中的配置为主,也就是 .properties 配置⽂件的优先级最⾼,但加载 完 .properties ⽂件之后,也会加载 .yml ⽂件的配置信息。实际的业务当中,我们通常会采取⼀种统⼀的配置⽂件格式,这样可以更好的维护(降低故障率)。
6.3.2.1 .properties 详解

properties 配置⽂件是最早期的配置⽂件格式,也是创建 Spring Boot 项⽬默认的配置⽂件。

  1. 语法:
    properties 是以键值的形式配置的,key 和 value 之间是以“=”连接。
# 配置项⽬端⼝号
# 配置⽂件中使⽤“#”来添加注释信息。
server.port=8084 
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?characterEncoding= utf8
spring.datasource.username=root
spring.datasource.password=root

从上述配置key看出,properties 配置⽂件中会有很多的冗余的信息:在这里插入图片描述

  1. 读取配置⽂件

主动的读取配置⽂件中的内容,可以使⽤ @Value 注解来实现,@Value 注解使⽤“${}”的格式读取。

@Component 在 Spring Boot 启动时候会注⼊到框架中,注⼊到框架中时会执⾏ @PostConstruct初始化⽅法,这个时候就能读取到配置信息了。

示例:

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

import javax.annotation.PostConstruct;

@Component
public class ReadYml { 
    @Value("${server.port}") 
    private String port;

    @PostConstruct
    public void postConstruct() { 
        System.out.println("Read YML,port:" + port); 
    }
}

在这里插入图片描述

6.3.2.2 .yml 详解

yml 是 YAML 是缩写,它的全称 Yet Another Markup Language 翻译成中⽂就是“另⼀种标记语⾔”。

  • yml 是⼀个可读性⾼,写法简单、易于理解,它的语法和 JSON 语⾔类似。
  • yml ⽀持更多的数据类型,它可以简单表达清单(数组)、散列表,标量等数据形态。它使⽤空⽩ 符号缩进和⼤量依赖外观的特⾊,特别适合⽤来表达或编辑数据结构、各种配置⽂件等。
  • yml ⽀持更多的编程语⾔,它不⽌是 Java 中可以使⽤在 Golang、PHP、Python、Ruby、 JavaScript、Perl 中。
  1. 语法
  • yml 是树形结构的配置⽂件,它的基础语法是“key: value”,注意 key 和 value 之间使⽤英⽂冒汗加空 格的⽅式组成的,其中的空格不可省略。在这里插入图片描述
  • 示例:使⽤ yml 连接数据库
spring:
 datasource:
   url: jdbc:mysql://127.0.0.0:3306/dbname?>characterEncoding=utf8 
   username: root
   password: root

properties:
在这里插入图片描述
yml:
在这里插入图片描述

  1. yml 配置读取

yml 读取配置的⽅式和 properties 相同,使⽤ @Value 注解即可。

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

import javax.annotation.PostConstruct;

@Component
public class ReadYml { 
    @Value("${string.hello}") 
    private String hello;

    @PostConstruct
    public void postConstruct() { 
        System.out.println("Read YML,Hello:" + hello); 
    }
}

在这里插入图片描述

配置不同数据类型及 null:

# 字符串 
string.value: Hello

#布尔值,true或false boolean.value: true boolean.value1: false

# 整数
int.value: 10
int.value1: 0b1010_0111_0100_1010_1110 # ⼆进制

# 浮点数
float.value: 3.14159
float.value1: 314159e-5 # 科学计数法

# Null,~代表null 
null.value: ~
  • 配置字符串:
    字符串默认不⽤加上单引号或者双引号,如果加英⽂的单双引号可以表示特殊的含义。
  1. 字符串默认不⽤加上单引号或者双引号。
  2. 单引号会转义特殊字符,特殊字符最终只是⼀个普通的字符串数据。
  3. 双引号不会转义字符串⾥⾯的特殊字符;特殊字符会作为本身想表示的意思。

配置:

string:
 str1: Hello \n Spring Boot. 
 str2: 'Hello \n Spring Boot.' 
 str3: "Hello \n Spring Boot."

读取:

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

import javax.annotation.PostConstruct;

@Component
public class ReadYml { 
   @Value("${string.str1}") 
   private String str1;

   @Value("${string.str2}") 
   private String str2;

   @Value("${string.str3}") 
   private String str3;

   @PostConstruct
   public void postConstruct() { 
       System.out.println("string.str1:" + str1); 
       System.out.println("string.str2:" + str2); 
       System.out.println("string.str3:" + str3); 
   }
}

执行结果:
在这里插入图片描述

  • 配置对象:
student: 
 id: 1 
 name: Java 
 age: 18
# 行内写法:
student: {id: 1,name: Java,age: 18}

读取对象: @ConfigurationProperties

# getter 和 setter ⽅法不能省略。

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@ConfigurationProperties(prefix = "student") 
@Component
public class StudentComponent {
  private int id;
   private String name;
   private int age;

   public int getId() { 
       return id;
   }

   public void setId(int id) { 
       this.id = id;
   }

   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 "StudentComponent{" + 
               "id=" + id + 
               ", name='" + name + '\'' + 
               ", age=" + age + 
               '}';
   }
}

调用类:

@Component
public class ReadYml2 {
   @Autowired
   private StudentComponent studentComponent;

   @PostConstruct
   public void postConstruct() { 
       System.out.println(studentComponent); 
   }
}

执行结果;
在这里插入图片描述

配置集合:

dbtypes:
 name:
   - mysql
   - sqlserver 
   - db2
# ⾏内写法
dbtypes: {name: [mysql,sqlserver,db2]}

读取集合: @ConfigurationProperties


@Component 
@ConfigurationProperties("dbtypes")
@Data
public class ListConfig { 
  private List<String> name;
}

打印类:

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

import javax.annotation.PostConstruct;

@Component
public class ReadYml2 { 
   @Autowired
   private ListConfig listConfig;

   @PostConstruct
public void postConstruct() { 
       System.out.println(listConfig.getName()); 
   }
}
6.3.2.3 properties VS yml
  1. properties 是以 key=value 的形式配置的键值类型的配置⽂件,⽽ yml 使⽤的是类似 json 格式的 树形配置⽅式进⾏配置的,yml 层级之间使⽤换⾏缩进的⽅式配置,key 和 value 之间使⽤“: ”英⽂冒号加空格的⽅式设置,并且空格不可省略。
  2. properties 为早期并且默认的配置⽂件格式,但其配置存在⼀定的冗余数据,使⽤ yml 可以很好的 解决数据冗余的问题。
  3. yml 通⽤性更好,⽀持更多语⾔,如 Java、Go、Python 等,如果是云服务器开发,可以使⽤⼀份 配置⽂件作为 Java 和 Go 的共同配置⽂件。
  4. yml ⽀持更多的数据类型。

SpringMVC程序开发

什么是MVC

MVC 是 Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分 为模型、视图和控制器三个基本部分。
在这里插入图片描述

  • Model(模型)是应⽤程序中⽤于处理应⽤程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应⽤程序中处理数据显示的部分。通常视图是依据模型数据创建的。
  • Controller(控制器)是应⽤程序中处理⽤户交互的部分。通常控制器负责从视图读取数据, 控制⽤户输⼊,并向模型发送数据。

什么是SpringMVC

  • Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为“SpringMVC”。
  • MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现。总结来说,Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web 框架,那么当⽤户在浏览器中输⼊了 url 之后, Spring MVC 项⽬就可以感知到⽤户的请求。
  • Spring MVC 是 Spring 框架的核⼼模块,⽽ Spring Boot 是 Spring 的脚⼿架,绝⼤部分的 Java 项⽬约等于 Spring MVC 项⽬。
  • 在创建 Spring Boot 项⽬时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架。在这里插入图片描述

Spring MVC 创建和连接

Spring MVC 项⽬创建和 Spring Boot 创建项⽬相同(Spring MVC 使⽤ Spring Boot 的⽅式创建), 在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项⽬。 Spring MVC 中使⽤ @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作⽤。

  1. 创建 Spring MVC 项⽬
    <<1>> Spring MVC 可以基于 Spring Boot 创建,也就是创建⼀个 Spring Boot 项⽬,勾选上 Spring Web 模块即可。在这里插入图片描述
    <<2>> 创建⼀个 UserController 类,实现⽤户到 Spring 程序的互联互通
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;

@Controller // 让 spring 框架启动时,加载 
@ResponseBody // 返回⾮⻚⾯数据 
@RequestMapping("/user") // 路由器规则注册 
public class UserController {
    // 路由器规则注册 
    @RequestMapping("/hi")
    public String sayHi(){
        return "<h1>Hi,Spring MVC.</h1>"; 
    }
}

<<3>> 当访问地址:http://localhost:8080/user/hi 时就能打印“hello,spring mvc”的信息了。

  • @RequestMapping 注解:
    @RequestMapping 是 Spring Web 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的路 由映射的。所谓的路由映射指的是,当⽤户访问⼀个 url 时,将⽤户的请求对应到程序中某个类 的某个⽅法的过程就叫路由映射。
  • @RequestMapping 修饰类:
import com.example.demo.model.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/p")
public class PersonController { 
   @RequestMapping("/index")
   public Object index(Person person){
       // 获取参数 
       System.out.println(person.getName() +":"+ 
               person.getPassword());
       // 执⾏业务...
       return "/index.html";
}
}
  • @RequestMapping 修饰类+修饰⽅法,当修饰类和⽅法时,访问的地址是类 + ⽅法。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import >org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody // 定义返回的数据格式为⾮视图(text/html) 
public class UserController { 
   @RequestMapping("/hi")
   public String sayHi(){
       return "<h1>Hi,Spring MVC.</h1>";
   }
}

⽅法地址:http://localhost:8080/hi

  • @RequestMapping默认接收GET请求,显示的指定 @RequestMapping 来接收 POST请求
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import >org.springframework.web.bind.annotation.RequestMethod; import >org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody // 定义返回的数据格式为⾮⻚⾯
public class UserController {
   @RequestMapping(value = "/hi",method= RequestMethod.POST) 
   public String sayHi(){
       return "<h1>Hi,Spring MVC.</h1>";
   }
}
  • GET请求的3种写法:
// 写法1 
@RequestMapping("/index")

// 写法2
@RequestMapping(value = "/index",method = RequestMethod.GET)

// 写法3 
@GetMapping("/index")
  • post 请求的 2 种写法:
// 写法1
@RequestMapping(value = "/index",method = RequestMethod.POST)

// 写法2 
@PostMapping("/index")

获取参数

  • 传递单个参数

在 Spring MVC 中可以直接⽤⽅法中的参数来实现传参。

示例:

@RequestMapping("/m1")
public Object method_1(String name){ 
   System.out.println("参数 name:"+name); 
   return "/index.html";
}

在这里插入图片描述
程序的执⾏结果:
在这里插入图片描述

  • 传递对象

Spring MVC 可以⾃动实现参数对象的赋值
示例:

定义对象:

import lombok.Data;

@Data
public class Person { 
    private int id; 
    private String name; 
    private String password;
}

传递对象代码实现:

@RequestMapping("/m2")
public Object method_2(Person p){ 
    System.out.println("对象中的 name:"+p.getName()); 
    System.out.println("对象中的 password:"+p.getPassword()); 
    return "/index.html";
}

前端访问:

在这里插入图片描述
最终执⾏结果:
在这里插入图片描述

  • 表单参数传递/传递多个参数(⾮对象)

当有多个参数时,前后端进⾏参数匹配时,是以参数的名称进⾏匹配的,因此参数的位置不影响后端获取参数的结果。

@RequestMapping("/m3")
public Object method_3(String name, String pwd) { 
    System.out.println("name 参数:" + name); 
    System.out.println("pwd 参数:" + pwd); 
    return "/index.html";
}

前台访问地址:
在这里插入图片描述

  • 后端参数重命名(后端参数映射)

某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,⽐如前端传递了⼀个 time 给后端,⽽后端⼜是有 createtime 字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使⽤ @RequestParam 来重命名前后端的参数值。

@RequestMapping("/m4")
public Object method_4(@RequestParam("time") String createtime) { 
    System.out.println("时间:" + createtime);
    return "/index.html";
}

前端访问地址:

在这里插入图片描述

  • 设置必传参数:
    如果我们是前端传递⼀个⾮ time 的参数,就会出现程序报错的情况:
    在这里插入图片描述
    这是因为后端@RequestParam 注解已经声明了前端必须传递⼀个 time 的参数,但是前端没有给后端传递,查看 @RequestParam 注解的实现细节:在这里插入图片描述
  • ⾮必传参数设置

如果我们的实际业务前端的参数是⼀个⾮必传的参数,我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错。

@RequestMapping("/m4")
public Object method_4(@RequestParam(value = "time", required = false) Stri ng createtime) {
    System.out.println("时间:" + createtime);
    return "/index.html";
}
  • @RequestBody 接收JSON对象
  • 在这里插入图片描述
    后端接收代码:
@RequestMapping(value = "/m5", method = RequestMethod.POST) public Object method_5(@RequestBody Person person) { 
    System.out.println("Person:" + person);
    return "redirect:/index.html";
}
  • 获取URL中参数@PathVariable

后端实现代码:

@PostMapping("/m6/{name}/{password}")
public Object method_6(@PathVariable String name, @PathVariable String pass word) {
    System.out.println("name:" + name);
    System.out.println("password:" + password);
    return "redirect:/index.html";
}

前端⽅法地址:

@PostMapping(“/m6/{name}/{password}”) 中的 {password} 参数不能省略

在这里插入图片描述

  • 上传⽂件@RequestPart
@RequestMapping("/param9")
public String param9(String name, @RequestPart("myfile") MultipartFile fil e) throws IOException {
    // 获取⽂件后缀名
    String fileName = file.getOriginalFilename().substring(file.getOrigina lFilename().lastIndexOf("."));
    // ⽂件保存地址
    String filePath = ClassUtils.getDefaultClassLoader().getResource("stat ic").getPath() +
         "/" + UUID.randomUUID() + fileName;
    // 保存⽂件
    file.transferTo(new File(filePath));
    return filePath + " 上传成功.";
}

在这里插入图片描述

获取项⽬⽬录的⼏种⽅式:

ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX).getPath();

new ClassPathResource("").getFile().getAbsolutePath(); 

ClassUtils.getDefaultClassLoader().getResource("").getPath(); 

ResourceUtils.getFile("classpath:static/").getPath();
  • 获取 Request 和 Response 对象
@RequestMapping("/param10")
public String param10(HttpServletResponse response, HttpServletRequest requ est) {
    String name = request.getParameter("name");
    // 获取所有 cookie 信息
    Cookie[] cookies = request.getCookies();
    return name + " 你好.";
}
  • 传统获取 header/cookie
@RequestMapping("/param10")
@ResponseBody
public String param10(HttpServletResponse response, HttpServletRequest requ est) {
    String name = request.getParameter("name");
    // 获取所有 cookie 信息
    Cookie[] cookies = request.getCookies();
    String userAgent = request.getHeader("User-Agent");
    return name + ":"+userAgent;
}
  • 简洁的获取 Cookie—@CookieValue
@RequestMapping("/cookie")
@ResponseBody
public String cookie(@CookieValue("bite") String bite) { 
    return "cookie:" + bite;
}
  • 简洁获取 Header—@RequestHeader
@RequestMapping("/header")
@ResponseBody
public String header(@RequestHeader("User-Agent") String userAgent) { 
    return "userAgent:"+userAgent;
}
  • Session 存储和获取
    Session 存储:
@RequestMapping("/setsess")
@ResponseBody
public String setsess(HttpServletRequest request) {
    // 获取 HttpSession 对象,参数设置为 true 表示如果没有 session 对象就创建⼀个 session
    HttpSession session = request.getSession(true);
    if(session!=null){
        session.setAttribute("username","java");
    }
    return "session 存储成功";
}

读取 Session :

@RequestMapping("/sess")
@ResponseBody
public String sess(HttpServletRequest request) {
    // 如果 session 不存在,不会⾃动创建
    HttpSession session = request.getSession(false);
    String username = "暂⽆";
    if(session!=null && session.getAttribute("username")!=null){ 
        username = (String) session.getAttribute("username");
    }
    return "username:"+username;
}
  • 获取 Session 更简洁的⽅式:
@RequestMapping("/sess2")
@ResponseBody
public String sess2(@SessionAttribute(value = "username",required = false) String username) {
    return "username:"+username;
}

返回数据

默认请求下⽆论是 Spring MVC 或者是 Spring Boot 返回的是视图 (xxx.html),⽽现在都是前后端分离的,后端只需要返给给前端数据即可,这个时候我们就需要使⽤ @ResponseBody 注解了。

@ResponseBody 返回的值如果是字符会转换成 text/html,如果返回的是对象会转换成 application/json 返回给前端。
@ResponseBody 可以⽤来修饰⽅法或者是修饰类,修饰类表示类中的所有⽅法都会返回 html 或者 json,⽽不是视图。

组合注解:@RestController,@RestController = @Controller + @ResponseBody。

  • 返回静态⻚⾯
    <<1>> 创建前端⻚⾯ index.html
    在这里插入图片描述
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1. 0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
    <title>hello,spring mvc</title>
    <script src="index.js"></script>
</head>
<body>
    <h1>Hello,Spring MVC.</h1>
</body>
</html>

创建控制器 controller:

import com.example.demo.model.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/p")
public class PersonController { 
    @RequestMapping("/index") 
    public Object index(){ 
        // 执⾏业务...
        // 返回view -> index.html 
        return "/index.html";
    }
}
  • 返回 text/html
@RequestMapping("/m7") @ResponseBody
public String method_7() { 
    return "<h1>Hello,HTML~</h1>";
}

在这里插入图片描述
在这里插入图片描述

示例:实现计算器功能

使⽤ postman 传递参数,或使⽤ form 表单的⽅式提交参数。

前端⻚⾯:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1. 0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
    <title>计算器示例</title>
</head>
<body>
    <form action="http://localhost:8080/calc/sum">
        <h1>计算器</h1>
        数字1<input name="num1" type="text"><br>
        数字2<input name="num2" type="text"><br>
        <input type="submit" value=" 点击相加 ">
    </form>
</body>
</html>

controler代码:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@Controller
@RequestMapping("/calc")
public class CalcController {
    @RequestMapping("/sum")
    public String sum(Integer num1,Integer num2){
        return String.format("<h1>计算的结果是:%d</h1><a href='javascript:h istory.go(-1);'>返回</a>",num1+num2);
    }
}

  • 返回 JSON 对象
@RequestMapping("/m8")
@ResponseBody
public HashMap<String, String> method_8() { 
    HashMap<String, String> map = new HashMap<>(); 
    map.put("Java", "Java Value"); 
    map.put("MySQL", "MySQL Value"); 
    map.put("Redis", "Redis Value");
    return map;
}

在这里插入图片描述
在这里插入图片描述
示例:实现登录功能,前端使⽤ ajax,后端返回 json 给前端

后端代码:

@RequestMapping(value = "/login")
    @ResponseBody
    public HashMap<String,Object> login(String username, String password){ 
        HashMap<String,Object> res = new HashMap<>();
        int succ = 200;
        if(username!=null && password!=null && 
        username.equals("admin") && password.equals("admin")){ 
            res.put("msg","登录成功");
        }else{
            res.put("msg","登录失败");
        }
        res.put("succ",succ);
        return res;
    }

前端代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1. 0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="js/jquery-1.9.1.min.js"></script> 
    <title>Document</title>
    <script>
        function mysub() {
            var username = jQuery("#username").val();
            var password = jQuery("#password").val(); 
            jQuery.getJSON("/user/login",
                {
                    "username":username, 
                    "password":password
                },
                function (result) {
                if(result.succ==200){ 
                    alert("返回结果:"+result.msg);
                }else{
                    alert("操作失败,请重试。");
                }
            });
        }
    </script>
</head>
<body>
    <div style="text-align: center;">
        <h1>登录</h1>
        ⽤户:<input id="username">
        <br>
        密码:<input id="password" type="password">
        <br>
        <input type="button" value=" 提交 " onclick="mysub()" style="margin -top: 20px;margin-left: 50px;">
    </div>
</body>
</html>
  • 请求转发或请求重定向
// 请求重定向 
@RequestMapping("/index")
public String index(){
    return "redirect:/index.html";
}

// 请求转发 
@RequestMapping("/index2")
public String index2(){
    return "forward:/index.html";
}

转发是服务器帮转的,⽽重定向是让浏览器重新请求另⼀个地址
请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
请求重定向地址发⽣变化,请求转发地址不发⽣变化。
请求重定向与直接访问新地址效果⼀直,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。

请求转发 forward 导致问题演示:

请求转发如果资源和转发的⻚⾯不在⼀个⽬录下,会导致外部资源不可访问。

@Controller
@RequestMapping("/user")
public class UserController { 
    @RequestMapping(value = "/index") 
    public String sayHi(){ 
        return "forward:/index.html"; 
    }
}

程序的⽬录:
在这里插入图片描述
程序的执⾏结果:
在这里插入图片描述
将转发 foward 换成重定向 redirect,⻚⾯就可以正常获取到外部资源 js 了。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/user")
public class UserController { 
    @RequestMapping(value = "/index") 
    public String sayHi(){ 
        return "redirect:/index.html"; 
    }
}

综合示例:
示例一:使⽤ ajax 实现登录功能,⽆需连接数据库,如果⽤户名和密码都是 root,则显示登录成功,可以直 接访问 main ⻚⾯,否则显示登录失败。每次访问 main ⻚⾯时,验证是否已经登录,如果登录了显示 欢迎信息,否则让⽤户先去登录⻚⾯登录。
示例二:带头像功能的注册功能实现。

6.4 SpringBoot 单元测试

什么是单元测试?

单元测试(unit testing),是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。
单元测试是开发者编写的⼀⼩段代码,⽤于检验被测代码的⼀个很⼩的、很明确的(代码)功能是否正确。执⾏单元测试就是为了证明某段代码的执⾏结果是否符合我们的预期。如果测试结果符合我们的预 期,称之为测试通过,否则就是测试未通过(或者叫测试失败)。

  • 可以⾮常简单、直观、快速的测试某⼀个功能是否正确
  • 使⽤单元测试可以帮我们在打包的时候,发现⼀些问题,因为在打包之前,所以的单元测试必须通 过,否则不能打包成功。
  • 使⽤单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进⾏任何改 变的情况下,测试功能。

6.4.1 Spring Boot 单元测试使用

Spring Boot 项⽬创建时会默认单元测试框架 spring-boot-test,⽽这个单元测试框架主要是依靠另⼀ 个著名的测试框架 JUnit 实现的, Spring Boot 项⽬创建时⾃动添加。

<dependency> 
 <groupId>org.springframework.boot</groupId> 
 <artifactId>spring-boot-starter-test</artifactId> 
 <scope>test</scope>
</dependency>

spring-boot-starter-test 的 MANIFEST.MF⾥⾯有具体的说明:在这里插入图片描述

  1. ⽣成单元测试类
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
    最终⽣成的代码:
import org.junit.jupiter.api.Test;


class UserControllerTest {

    @Test 
    void save() {

    }
}

这个时候,此⽅法是不能调⽤到任何单元测试的⽅法的,此类只⽣成了单元测试的框架类,具体的业务 代码要⾃⼰填充。

  1. 添加单元测试代码
    <<1>> 添加 Spring Boot 框架测试注解:@SpringBootTest
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class UserControllerTest {

    @Test 
    void save() {

    }
}

<<2>> 添加单元测试业务逻辑

import com.example.demo.model.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class UserControllerTest {

    @Resource
    private UserController userController;

    @Test
    void save() {
        User user = new User(); 
        user.setId(1);
        user.setName("Java Test"); 
        user.setPassword("123");
        boolean result = userController.save(user); 
        // 使⽤断⾔判断最终的结果是否符合预期 
        Assertions.assertEquals(true, result); 
        //        Assertions.assertTrue(result);
    }
}

如果断⾔失败,则后⾯的代码都不会执⾏在这里插入图片描述

6.8 SpringBoot 日志文件

日志的功能

  • 发现和定位问题
  • 记录⽤户登录⽇志,⽅便分析⽤户是正常登录还是恶意破解⽤户
  • 记录系统的操作⽇志,⽅便数据恢复和定位操作⼈
  • 记录程序的执⾏时间,⽅便为以后优化程序提供数据⽀持

日志的使用

Spring Boot 内置了⽇志框架,默认情况下使⽤的是 info ⽇志级别将⽇志输出到控制台的。
在这里插入图片描述

⾃定义⽇志打印

  1. 在程序中得到⽇志对象

在程序中获取⽇志对象需要使⽤⽇志⼯⼚ LoggerFactory。
在这里插入图片描述

Logger 对象是属于 org.slf4j 包下的,不要导⼊错包,Spring Boot 中内置了⽇志框架 Slf4j,可以直接在程序中调⽤ slf4j 来输出⽇志。在这里插入图片描述

代码示例:


	private static Logger logger = LoggerFactory.getLogger(UserController.class );

  1. 使⽤⽇志对象打印⽇志
// 2.使⽤⽇志打印⽇志 
logger.info("--------------要输出⽇志的内容----------------");

在这里插入图片描述
在这里插入图片描述

⽇志级别

⽇志级别的作用
  1. ⽇志级别可以帮你筛选出重要的信息,⽐如设置⽇志级别为 error,那么就可以只看程序的报错⽇ 志了,对于普通的调试⽇志和业务⽇志就可以忽略了,从⽽节省开发者信息筛选的时间。
  2. ⽇志级别可以控制不同环境下,⼀个程序是否需要打印⽇志,如开发环境我们需要很详细的信息, ⽽⽣产环境为了保证性能和安全性就会输⼊尽量少的⽇志,⽽通过⽇志的级别就可以实现此需求。
⽇志级别的分类
  • trace:微量,少许的意思,级别最低
  • debug:需要调试时候的关键信息打印
  • info:普通的打印信息(默认⽇志级别)
  • warn:警告,不影响使⽤,但需要注意的问题
  • error:错误信息,级别较⾼的错误⽇志信息
  • fatal:致命的,因为代码异常导致程序退出执⾏的事件

越往上接收到的消息就越少,如设置了 warn 就只能收到 warn、error、fatal 级别的⽇志了。
在这里插入图片描述

⽇志级别的设置

⽇志级别配置只需要在配置⽂件中设置“logging.level”配置项即可。⽇志的输出级别,默认是 info。

示例:


logging: 
  level: 
    root: error
    
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

@RestController 
@RequestMapping("/user") 
public class UserController {

    // 1.得到⽇志对象
    private static Logger logger = 
            LoggerFactory.getLogger(UserController.class);

    @Value("${server.port}") 
    private String port;

    @Value("${spring.datasource.url}") 
    private String url;

    @RequestMapping("/sayhi") 
    public String sayHi() { 
        // 2.使⽤⽇志打印⽇志

        logger.trace("================= trace ==============="); 
        logger.debug("================= debug ==============="); 
        logger.info("================= info ===============");
        logger.warn("================= warn ==============="); 
        logger.error("================= error ===============");

        return "Hi," + url; 
    }
}
日志持久化

在⽣产环境上咱们需要将⽇志保存下来,以便出现问题之后追 溯问题,把⽇志保存下来的过程就叫做持久化。在配置⽂件中指定⽇志的存储⽬录或者是指定⽇志保存⽂件名之后, Spring Boot 就会将控制台的⽇志写到相应的⽬录或⽂件下了。

示例一:

# 设置⽇志⽂件的⽬录
logging:
  file:
    path: D:\\home\\ruoyi
# 设置⽇志⽂件的⽂件名
logging:
  file:
    name: D:\\home\\ruoyi\\spring-1204.log

示例二:
controller 包下 error 级别以上的⽇志保存到 log_all.log 下,将 service 下 warn 级别以上的⽇志保存到 log_all.log 下。

  • 实现的关键步骤:
    不同包定义不同的⽇志级别。
    使⽤⽇志对象打印所有类型的⽇志。
    设置固定的⽇志保存⽂件名。
使用Lombok输出日志

使⽤ @Slf4j 注解,在程序中使⽤ log 对象即可输⼊⽇志,并且只能使⽤ log 对象才能输出, 这是 lombok 提供的对象名。

  1. 添加 lombok 依赖
<dependency> 
 <groupId>org.projectlombok</groupId> 
    <artifactId>lombok</artifactId> 
    <version>1.18.20</version> 
    <optional>true</optional> 
</dependency>
  1. 输出⽇志
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

@RestController 
@RequestMapping("/p")
@Slf4j
public class PersonController {

    @RequestMapping("/log")
    public void loggerTest() { 
        log.error("------------------- error -----------------"); 
    }
}

lombok 原理
在这里插入图片描述
打开target目录:
在这里插入图片描述
在这里插入图片描述
lombok 的更多注解:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

6.6 SpringBoot 集成Redis

  1. 添加 redis 依赖
    在这里插入图片描述
    或将以下配置添加到 pom.xml 中:

<dependency>
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  1. 配置 redis
spring.redis.database=0 
spring.redis.port=6379 
spring.redis.host=82.157.146.10
# 可省略
spring.redis.lettuce.pool.min-idle=5 
spring.redis.lettuce.pool.max-idle=10 
spring.redis.lettuce.pool.max-active=8 
spring.redis.lettuce.pool.max-wait=1ms 
spring.redis.lettuce.shutdown-timeout=100ms
  1. ⼿动操作 redis
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class RedisController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 // 在 redis 存储数据
    @RequestMapping("/setrs")
    public String setRedis(String name, String value) { 
        stringRedisTemplate.opsForValue().set(name, value, 
                30, TimeUnit.SECONDS);
        return "Set redis success.";
    }
 // 读取 redis 中的数据
    @RequestMapping("/getrs")
    public String getRedis(String name) {
        Object valObj = stringRedisTemplate.opsForValue().get(name); 
        if (valObj != null) return valObj.toString();
        return "Null";
    }
}
  1. 注解操作 redis

<<1>> 开启缓存


@SpringBootApplication
@EnableCaching

<<2>> 注解操作 redis

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisAnnotationController {

    @Cacheable(value = "spring:cache", key = "#name") 
    @RequestMapping("/setrs")
    public String setRedis(String name, String value) {
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(value)) 
 {
            return "请先输⼊ name 和 value";
        }
        return value;
    }

    @RequestMapping("/uprs")
    @CachePut(value = "spring:cache", key = "#name")
    public String updateRedis(String name, String value) {
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(value)) 
 {
            return "请先输⼊ name 和 value";
        }
        return value;
    }

    @RequestMapping("/delrs")
    @CacheEvict(value = "spring:cache", key = "#name") 
    public String delUser(String name) {
        return "delete success";
    }
}

MyBatis查询数据库

MyBatis 是什么?

MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。
MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏ 乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
在这里插入图片描述

  • JDBC 的操作流程:
  1. 创建数据库连接池 DataSource
  2. 通过 DataSource 获取数据库连接 Connection
  3. 编写要执⾏带 ? 占位符的 SQL 语句
  4. 通过 Connection 及 SQL 创建操作命令对象 Statement
  5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值 6. 使⽤ Statement 执⾏ SQL 语句
  6. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
  7. 处理结果集
  8. 释放资源

对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参 数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等, ⽽所有的这些操作步骤都需要在每个⽅法中重复书写。

MyBatis 在整个框架中的定位,框架交互流程图:

在这里插入图片描述
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在⾯向 >对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象 >的互相转换:

  1. 将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL
  2. 将结果集映射为返回对象,即输出对象
    ORM 把数据库映射为对象:
    数据库表(table)–> 类(class)
    记录(record,⾏数据)–> 对象(object)
    字段(field) --> 对象的属性(attribute)
    ⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间 的转换。

MyBatis的基本使用

创建数据库和表

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使⽤数据数据 use mycnblog;

-- 创建表[⽤户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment, 
    username varchar(100) not null, 
    password varchar(32) not null, 
    photo varchar(500) default '', 
    createtime datetime default now(), 
    updatetime datetime default now(), 
    `state` int default 1
) default charset 'utf8mb4';

-- 创建⽂章表
drop table if exists  articleinfo; 
create table articleinfo(
    id int primary key auto_increment, 
    title varchar(100) not null, 
    content text not null, 
    createtime datetime default now(), 
    updatetime datetime default now(), 
    uid int not null,
    rcount int not null default 1, 
    `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
   vid int primary key,
   `title` varchar(250),
   `url` varchar(1000), 
  createtime datetime default now(), 
  updatetime datetime default now(), 
   uid int
)default charset 'utf8mb4';

-- 添加⼀个⽤户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
;

-- ⽂章添加测试数据
insert into articleinfo(title,content,uid) 
    values('Java','Java正⽂',1);

-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://ww w.baidu.com',1);

添加MyBatis框架⽀持

  1. ⽼项⽬添加MyBatis

添加框架⽀持:

<!-- 添加 MyBatis 框架 -->
<dependency> 
            <groupId>org.mybatis.spring.boot</groupId> 
            <artifactId>mybatis-spring-boot-starter</artifactId> 
            <version>2.1.4</version>
</dependency>
<!-- 添加 MySQL 驱动 -->
<dependency>
            <groupId>mysql</groupId> 
            <artifactId>mysql-connector-java</artifactId> 
            <version>5.1.38</version> 
            <scope>runtime</scope>
</dependency>

使⽤EditStarters插件快速添加:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

  1. 新项⽬添加MyBatis
    新项⽬创建 Spring Boot 项⽬的时候添加引⽤就可以了。
    在这里插入图片描述

配置连接字符串和MyBatis

  1. 配置连接字符串
    在application.yml 添加如下内容:

如果使⽤ mysql-connector-java 是 5.x 之前的使⽤的是“com.mysql.jdbc.Driver”,如果是⼤于 5.x 使⽤的是“com.mysql.cj.jdbc.Driver”

# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL =false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  1. 配置 MyBatis 中的 XML 路径
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件 mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

添加业务代码

后端开发的工程思路:
在这里插入图片描述

  1. 添加实体类
import lombok.Data; 
import java.util.Date;

@Data
public class User { 
    private Integer id; 
    private String username; 
    private String password; 
    private String photo; 
    private Date createTime; 
    private Date updateTime;
}
  1. 添加 mapper 接⼝
import org.apache.ibatis.annotations.Mapper; import java.util.List;

@Mapper
public interface UserMapper {

    public List<User> getAll();

}
  1. 添加 UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

</mapper>

UserMapper.xml 查询所有⽤户的具体实现 SQL:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.model.User"> 
        select * from userinfo
    </select>
</mapper>


<mapper>标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接⼝的全限定
名,包括全包名.类名。

<select>查询标签:是⽤来执⾏数据库的查询操作的:
id:是和 Interface(接⼝)中定义的⽅法名称⼀样的,表示对接⼝的具体实现⽅法。
resultType:是返回的数据类型,也就是开头我们定义的实体类。

  1. 添加 Service
import org.springframework.stereotype.Service;

import javax.annotation.Resource; 
import java.util.List;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public List<User> getAll() { 
        return userMapper.getAll(); 
    }
}
  1. 添加 Controller
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource; 
import java.util.List;

@RestController 
@RequestMapping("/u")
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping("/getall") 
    public List<User> getAll(){ 
        return userService.getAll(); 
    }

}
  1. 使⽤ postman 测试
    在这里插入图片描述

增、删、改操作

增加操作

controller 实现代码:

@RequestMapping(value = "/add",method = RequestMethod.POST) public Integer add(@RequestBody User user){
    return userService.getAdd(user);
}

mapper interface:

 Integer add(User user);

mapper.xml

<insert id="add">
  insert into userinfo(username,password,photo,state) 
  values(#{username},#{password},#{photo},1)
</insert>

Postman 添加访问:
默认情况下返回的是受影响的⾏数。

{"username":"mysql","password":"mysql","photo":"img.png"}

在这里插入图片描述

返回⾃增 id

controller 实现代码:

@RequestMapping(value = "/add2", method = RequestMethod.POST) public Integer add2(@RequestBody User user) { 
    userService.getAdd2(user);
    return user.getId();
}

mapper 接⼝:

@Mapper
public interface UserMapper {

    // 添加,返回⾃增id 
    void add2(User user);
}

mapper.xml 实现如下:

<!-- 返回⾃增id -->
<insert id="add2" useGeneratedKeys="true" keyProperty="id"> 
    insert into userinfo(username,password,photo,state) 
    values(#{username},#{password},#{photo},1)
</insert>
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据 库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动 递增字段),默认值:false。
  • keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列 不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性 名称。
  • keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回 值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列 不⽌⼀个,可以⽤逗号分隔多个属性名称。

postman 返回结果:
在这里插入图片描述

修改⽤户操作

controller:

/**
     * 修改操作
     *
     * @param id
     * @param name
     * @return
     */
    @RequestMapping("/update")
    public Integer update(Integer id, String name) { 
        return userService.update(id, name);
    }

mapper.xml 实现代码:

<update id="update">
    update userinfo set username=#{name} where id=#{id} 
</update>

删除⽤户操作

<delete id="delById" parameterType="java.lang.Integer"> 
    delete from userinfo where id=#{id}
</delete>

查询操作详解

参数占位符 #{} 和 ${}

  1. #{}:预编译处理。
  2. ${}:字符直接替换。
  • 预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
  • 使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。

SQL 注⼊问题

<select id="isLogin" resultType="com.example.demo.model.User">
    select * from userinfo where username='${name}' and password='${pwd}' </select>

sql 注⼊代码:“’ or 1='1”

在这里插入图片描述

单表查询

⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式。
Controller 实现代码如下:

@RequestMapping("/getuser")
public User getUserById(Integer id) { 
    return userService.getUserById(id);
}

Mapper.xml 实现代码如下:

<select id="getUserById" resultType="com.example.demo.model.User"> 
    select * from userinfo where id=#{id}
</select>

like 查询

like 使⽤ #{} 报错,相当于: select * from userinfo where username like ‘%‘username’%’。

 <select id="findUserByName2" resultType="com.example.demo.model.User"> 
         select * from userinfo where username like '%#{username}%'; 
 </select>

使⽤ mysql 的内置函数 concat() 来处理:

<select id="findUserByName3" resultType="com.example.demo.model.User"> 
         select * from userinfo where username like concat('%',#{usernam e},'%');
</select>

多表查询

返回值类型

如果是增、删、改返回搜影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的。
在这里插入图片描述

查询不设置返回类型会报错:
controller 代码:

@RequestMapping("/getname")
public String getNameById(Integer id) { 
    return userService.getNameById(id);
}

mapper.xml 实现代码:

<select id="getNameById">
    select username from userinfo where id=#{id} 
</select>

访问接⼝执⾏结果显示运⾏了⼀个查询但没有找到结果映射:
在这里插入图片描述

查询标签来说⾄少需要两个属性:
id 属性:⽤于标识实现接⼝中的那个⽅法;
结果映射属性:

结果映射有两种实现标签: 和 。

resultType

绝⼤数查询场景可以使⽤ resultType 进⾏返回,使⽤⽅便,直接定义到某个实体类即可。

<select id="getNameById" resultType="java.lang.String"> 
    select username from userinfo where id=#{id} 
</select>
resultMap

字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。

  • 字段名和属性名不同的情况
    在这里插入图片描述
    程序中的属性:
    在这里插入图片描述
    mapper.xml 代码:
<select id="getUserById" resultType="com.example.demo.model.User"> 
  select * from userinfo where id=#{id}
</select>

使用resultType查询的结果:
在这里插入图片描述
使用resultMap查询:
在这里插入图片描述
mapper.xml:

<resultMap id="BaseMap" type="com.example.demo.model.User"> 
    <id column="id" property="id"></id>
    <result column="username" property="username"></result> 
    <result column="password" property="pwd"></result> 
</resultMap>

<select id="getUserById" resultMap="com.example.demo.mapper.UserMapper.Base Map">
    select * from userinfo where id=#{id}
</select>

查询结果:
在这里插入图片描述

多表查询

在多表查询时,如果使⽤ resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象的
实体类:

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content; 
    private LocalDateTime createtime; 
    private LocalDateTime updatetime; 
    private Integer rcount;
    // 包含了 userinfo 对象
    private UserInfo user;
}

程序的执⾏结果:
在这里插入图片描述

⼀对⼀的表映射

⼀对⼀映射要使⽤ 标签,具体实现(⼀篇⽂章只对应⼀个作者):

<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo"> 
   <id property="id" column="id"></id>
   <result property="title" column="title"></result>
   <result property="content" column="content"></result>
   <result property="createtime" column="createtime"></result> 
   <result property="updatetime" column="updatetime"></result> 
   <result property="uid" column="uid"></result>
   <result property="rcount" column="rcount"></result>
   <result property="state" column="state"></result>
   <association property="user" 
                resultMap="com.example.demo.mapper.UserMapper.BaseMap" 
                columnPrefix="u_">
   </association>
</resultMap>
<select id="getAll" resultMap="BaseMap">
  select a.*,u.username u_username from articleinfo a
  left join userinfo u on a.uid=u.id
</select>

8. Spring Session 持久化

6.7 SpringBoot 统一功能处理

maven包下载失败问题, 更换国内镜像

6.5 SpringBoot 项目发布

打包 Spring Boot 项目

使⽤ Maven 打包 Spring Boot 项⽬。
在打包项⽬的时候,⼀定要检查,确保数据库连接的是远程服务器的 MySQL,确保密码正确。
在这里插入图片描述在这里插入图片描述

准备好线上环境

  1. 确认服务器已安装好 Java 环境
  2. 确保服务器有可⽤的 MySQL(MySQL 服务端已安装好)

上传 Jar 包⾄服务器

随⽤任意 ftp ⼯具,将打包好的 Jar 包上传⾄服务器,⽐如 xshell/xftp/finalshell 都可以。

启动项目

使⽤以下命令启动 Spring Boot 项⽬:

	nohup java -jar xxx.jar &

停⽌项目

  1. 查询出运⾏的 Spring Boot 的进程
	ps -ef|grep java

在这里插入图片描述

  1. 将 Spring Boot 的进程结束掉
	kill -9 进程ID

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/569699.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud学习笔记(Ribbon):Ribbon的应用样例

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、Ribbon简介1.1、架构图1.2、简单实现负载均衡 2、配置负载均衡策略2.1、IRule2.2、使用IRule简单示例2.2.1、Overview2.2.1、注入IRule2.2.2、关联IRule和服务 1、Ribbon简介 我们都知道Ribbon是用于负载均衡…

5-内核开发-/proc File System 学习

5-内核开发-/proc File System 学习 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中。…

Nacos采坑:非集群Nacos不要使用同一个MySQL数据库

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Nacos 致力于帮助您…

第27章 筹集资金

< 回到目录 第六部分 流程 在各关键职能安排好了关键人员之后&#xff0c;公司有效运作&#xff0c;数据系统正常运行&#xff0c;经理和团队成员之间的双向信息交流顺畅。现在&#xff0c;剩下的就是你与外部世界的交流&#xff0c;包括与投资者、招聘者和客户的互动。这些…

银行买的黄金怎么卖出去?了解黄金交易的步骤和注意事项

黄金一直以来都是备受投资者关注的贵金属之一。银行提供了购买黄金的机会&#xff0c;但投资者也需要了解如何卖出银行买的黄金。 选择适合的购买方式 投资者可以通过多种途径购买黄金&#xff0c;其中包括银行提供的黄金交易服务。银行买黄金的方式可以是通过黄金交易账户、黄…

力扣HOT100 - 114. 二叉树展开为链表

解题思路&#xff1a; class Solution {List<TreeNode> list new ArrayList<>();public void flatten(TreeNode root) {recur(root);for (int i 1; i < list.size(); i) {TreeNode pre list.get(i - 1);TreeNode cur list.get(i);pre.left null;pre.right…

SpringBoot学习之Kafka下载安装和启动【Mac版本】(三十三)

一、配置Java环境变量 在启动Kafka之前,你需要先正确配置好你的Java环境变量。可以在终端输入java -version检查java环境变量是否配置正确,在Mac上如何配置java环境变量,请读者自行网上搜索操作之,此处不赘叙。 二、下载安装Kafka 1、下载Kafka:Apache Kafka,这两个版本…

python简易小时钟

import time import turtledef getTime():tt time.localtime() # 结构化的时间ss time.strftime(%Y年%m月%d日 %H:%M:%S, tt)return sspen turtle.Turtle()pen.backward(100) pen.speed(0)while True:time.sleep(1)times getTime()pen.clear()pen.write(times, font("…

中颖51芯片学习10. Touch Key触摸按键功能

中颖51芯片学习10. Touch Key触摸按键功能 一、SH79F9476 资源介绍1. 特性2. 系统框图&#xff1a;3.准备环境 二、准备工具三、开发步骤1. 新建项目流程&#xff08;1&#xff09;新建工程&#xff08;2&#xff09;选择芯片和封装&#xff08;3&#xff09;触摸配置按键&…

Tomcat架构设计精髓分析-Connector高内聚低耦合设计

优秀的模块化设计通常都会采用高内聚、低耦合 高内聚是指相关度比较高的功能要尽可能集中&#xff0c;不要分散。低耦合是指两个相关的模块要尽可能减少依赖的部分和降低依赖的程序&#xff0c;不要让两个模块产中强依赖。 Tomca连接器需要实现的功能: 监听网络端口 接受网络…

MATLAB将多张小图整合到一张大图形成模板图

MATLAB将多张小图整合到一张大图形成模板图 代码如下: clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;foldername字符模板; [datacell,filenamecell,filenameAllcell]readfun_1n(foldername); K2length(filenamecell);% …

用友 GRP-U8 fastjson远程代码执行漏洞复现(XVE-2024-8863)

0x01 产品简介 用友GRP-U8R10行政事业内控管理软件是用友公司专注于国家电子政务事业,基于云计算技术所推出的新一代产品,是我国行政事业财务领域最专业的政府财务管理软件。 0x02 漏洞概述 用友 GRP-U8 R10系列版本 VerifyToken 接口存在低版本fastjson反序列化漏洞,未经…

ESP32环境下基于SD卡与FTP实现温湿度数据采集与存储

本篇文章将介绍如何利用ESP32开发板结合SD卡与FTP服务器功能&#xff0c;实现温湿度数据的实时采集、存储与远程访问。项目代码基于Arduino IDE平台编写&#xff0c;主要依赖于以下库&#xff1a; SPIDHTWiFitime.hESP-FTP-Server-LibSDSPIFFSFS 1. 硬件连接与配置 1.1 所需…

Python | Leetcode Python题解之第46题全排列

题目&#xff1a; 题解&#xff1a; class Solution:def permute(self, nums):""":type nums: List[int]:rtype: List[List[int]]"""def backtrack(first 0):# 所有数都填完了if first n: res.append(nums[:])for i in range(first, n):# 动…

漫谈AI 时代的信息模型

模型化- 数字化转型的重要基石 在各行各业推行数字化转型过程中&#xff0c;构建信息化模型十分重要&#xff0c;它是数字化转型的基石。事实上&#xff0c;数字化转型的核心是“万物皆模型”&#xff0c;在工业领域&#xff0c;以德国为主导的工业4.0 发展进程中&#xff0c;…

超越5G:迈向6G网络传感前沿的革命性飞跃

6G推动的传感技术发展有望将人类感官拓展到目前的极限。从精确测绘到活动识别&#xff0c;联网传感揭示了看不见的事物&#xff0c;使我们能够理解和预测以前未知的周围环境。高精度定位、增强人类感官和手势/活动识别的无缝集成预示着未来机器将以前所未有的自主性运行... 在飞…

安全AI未来 | C3安全大会 · 2024,数据驱动 AI原生

数字为时代变革注入动力&#xff0c;AI为重塑社会文明带来原力。数智浪潮中&#xff0c;我们见证着时代跃迁的巨变&#xff0c;面临着适变、应变、驭变的挑战。 数字驱动、AI原生。数字的流动不仅承载着信息&#xff0c;更将激活未来的无限价值&#xff1b;AI&#xff0c;不…

为什么要写技术方案?

技术方案是为研究解决各类技术问题&#xff0c;有针对性&#xff0c;系统性的提出的方法、应对措施及相关对策。技术方案设计是一个技术开发者必备的能力&#xff0c;特别是对于高级、资深、架构师等角色。技术方案设计不仅能够帮助我们明确需求&#xff0c;规划架构&#xff0…

【数据库】三、数据库SQL语言命令(基础从入门到入土)

【全文两万多字&#xff0c;涵盖大部分常见情况&#xff0c;建议点赞收藏】 目录 文章目录 目录安装SQL语言1.使用2.DATABASE查看所有库新建数据库修改数据库删除数据库连接数据库 3.TABLE创建表查看库所有表删除表查看表信息重命名表修改表字段&#xff08;列&#xff09;表中…

自动批量将阿里云盘文件发布成WordPress文章脚本源码(以RiPro主题为例含付费信息下载地址SEO等自动设置)源码

背景 很多资源下载站&#xff0c;付费资源下载站&#xff0c;付费内容查看等都可以用WordPress站点发布内容&#xff0c;这些站点一般会基于一个主题&#xff0c;付费信息作为文章附属的信息发布&#xff0c;底层存储在WP表里&#xff0c;比如日主题&#xff0c;子比主题等。 …