原来有分析过,这里在学习一遍。下面的分析理解,需要有aop,ioc,注解组件等基础。如果没有不建议看。
创建maven工程,并导包。我们分析的版本5.2.6。
spring核心包:aop、beans、context、core、expression、jcl
spring-aop:5.2.6.RELEASE
spring-beans:5.2.6.RELEASE
spring-context:5.2.6.RELEASE
spring-core:5.2.6.RELEASE
spring-expression:5.2.6.RELEASE
spring-jcl:5.2.6.RELEASE
我们先来看看使用场景:
先给一个pojo对象:
package com.stu.pojo;
public class User {
public User(String name, String habit) {
this.name = name;
this.habit = habit;
}
private String name;
private String habit;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHabit() {
return habit;
}
public void setHabit(String habit) {
this.habit = habit;
}
}
通过注解加载pojo对象:
package com.stu.config;
import com.stu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 配置类注解:告诉spring这个类为配置类。
@Configuration
public class UserConfig {
//给spring容器中注入一个bean:类型为user,ID为默认方法名,也可以设置
//@Bean
@Bean("getUserBean")
public User getUser(){
return new User("zhouyi","爱美女");
}
}
注解调用方式:
import com.stu.config.UserConfig;
import com.stu.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 使用方式
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
User user = applicationContext.getBean(User.class);
System.out.println(user.getName() + user.getHabit());
// 如何拿到的bean名
String[] strs = applicationContext.getBeanNamesForType(User.class);
for (String str : strs) {
System.out.println(str);
}
}
}
结果:
这个过程怎么实现的呢?接着看其他注解。
以下注解如何自动加载的,肯定是@ComponentScan或@ComponentScans:
@Controller、@Service、@Repository、@Component
来写一个正常的框架逻辑看看加载了那些bean:
先创建对应注解的类:
核心类:(即初始加载类)
这里不得不提以下。下面我配置了扫描bean的过滤器。实际中过滤器也可以用这种方式实现,但不推荐。
package com.stu.config;
import com.stu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
// 配置类注解:告诉spring这个类为配置类。
// excludeFilters 过滤掉;includeFilters 只加载
@Configuration
@ComponentScan(basePackages = "com.stu", excludeFilters = {
// 有按注解、正则,apectj、CUSTOM过滤扫描
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class UserConfig {
//给spring容器中注入一个bean:类型为user,ID为默认方法名,也可以设置
//@Bean
@Bean("getUserBean")
public User getUser(){
return new User("zhouyi","爱美女");
}
}
注解、ASSIGNABLE_TYPE、正则、apectj、CUSTOM的解释:
注解如上;
ASSIGNABLE_TYPE:按照指定的类型加载。
aspectj:使用aspectj表达式。
正则:按正则加载。
CUSTOM:自定义指定加载类型。
测试类:查看当前已被加载的bean名字。
import com.stu.config.UserConfig;
import com.stu.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
@Test
public void testComponnet() {
// 使用方式 加载你的入口类:即带有扫描类ComponentScan 我这里是UserConfig类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
// 当前beans名字的数组
String[] strs = applicationContext.getBeanDefinitionNames();
for (String str : strs) {
System.out.println(str);
}
}
}
结果如下:
1,Spring自己需要bean被加载。
2,我们自己写的被加载,并且过滤掉了被@Controller、@Service注解的类:
bean组件的作用域:
用注解@Scope 指定singleton单例、prototype多例、request同一次请求创建一个实例、session同一个session创建一个实例。
先看默认即单例:
package com.stu.config;
import com.stu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class SingleConfig {
/*
用注解@Scope 指定singleton单例、prototype多例、request同一次请求创建一个实例、session同一个session创建一个实例。
*/
@Scope
@Bean("user")
public User getUser(){
return new User("zhouyi","爱美女");
}
}
测试:
@Test
public void testComponnet1() {
// 使用方式 加载你的入口类:即带有扫描类ComponentScan 我这里是UserConfig类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingleConfig.class);
// 当前beans名字的数组
String[] strs = applicationContext.getBeanDefinitionNames();
for (String str : strs) {
System.out.println(str);
}
// spring 默认单实例
Object obj1 = applicationContext.getBean("user");
Object obj2 = applicationContext.getBean("user");
System.out.println(obj1 == obj2);
// 修改作用域@Scope
}
@Scope("prototype")
运行结果:发现每次没有都会加载,为多例。
另外两个需要web支持。暂不描述。
懒加载(@Lazy):一般单实例bean,容器启动的时候创建的对象。我们在容器启动时不创建对象,在第一次使用的时候去创建bean对象,我们称为懒加载(这个没什么多说的)。
框架常用的注解(@Conditional): 根据一定条件给容器中注册bean。
比如你这个项目中有多个过滤器子系统。根据你的条件注册不同的bean。
我们写两个不同环境的bean: user1和user2。根据Conditional不同的条件加载。
package com.stu.config;
import com.stu.filter.BSPFilter;
import com.stu.filter.SSOFilter;
import com.stu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SingleConfig {
@Conditional({BSPFilter.class})
@Bean("user1")
public User getUser1(){
return new User("zhouyi","爱美女");
}
@Conditional({SSOFilter.class})
@Bean("user2")
public User getUser2(){
return new User("zhouyi","喜欢大长腿");
}
}
判断当前系统为windows加载:
package com.stu.filter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class BSPFilter implements Condition {
/**
* 查看匹配
* @param context 匹配的上下文
* @param annotatedTypeMetadata 注释信息
* @return boolean
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 通过上下文能拿到如下
// 1、获取到IOC使用的beanFactory
ConfigurableBeanFactory beanFactory = context.getBeanFactory();
// 2、获取类加载器
ClassLoader loader = context.getClassLoader();
// 3、获取当前环境信息
Environment environment = context.getEnvironment();
// 4、获取到bean定义的注册类:可注册添加、移除、判断、获取bean
BeanDefinitionRegistry registry = context.getRegistry();
// 5、获取资源文件
ResourceLoader resourceLoader = context.getResourceLoader();
// 以操作系统举例
String str = environment.getProperty("os.name");
if (str.toLowerCase().contains("window")) {
return true;
}
return false;
}
}
直接返回false:
package com.stu.filter;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class SSOFilter implements Condition {
/**
* 查看匹配
* @param context 匹配的上下文
* @param annotatedTypeMetadata 注释信息
* @return boolean
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 直接返回false
return false;
}
}
bean注入另一种方式:
@Import注解:常用于导入第三方包,通过快速导入的方式实现把实例加入spring的IOC容器。
使用几种方式(一笔带过):
第一种:直接在入口类加上或者你需要的类上
@Import({ 类名.class , 类名.class... })
第二种自定义:
// 实现接口ImportSelector
public class Myclass implements ImportSelector {
/*
* annotationMetadata 当前标注@import注解的类的所有注解信息
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 这个返回的数组里是需要自动注册的全类名。可以通过annotationMetadata获取到各种注解信息。
return new String[0];
}
}
第三种:
public class Myclass2 implements ImportBeanDefinitionRegistrar {
/*
* annotationMetadata 当前标注@import注解的类的所有注解信息
* BeanDefinitionRegistry bean注册类。
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 拿到annotationMetadata中的信息,将我们需要注册的直接注册到beanDefinitionRegistry。加入前判断容器无,加入。
// 比如
registry.registerBeanDefinition("user2", new RootBeanDefinition(User.class));
}
}
还有一种bean的注册方式:
使用FactoryBean工厂注入bean。
实现FactoryBean,并指定对象User
package com.stu.factory;
import com.stu.pojo.User;
import org.springframework.beans.factory.FactoryBean;
public class FactorybeanSample implements FactoryBean<User> {
public User getObject() throws Exception {
return new User("工厂周", "搬砖");
}
public Class<?> getObjectType() {
return null;
}
// 控制是否单例
public boolean isSingleton() {
return false;
}
}
注册工厂bean:
package com.stu.config;
import com.stu.factory.FactorybeanSample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SingleConfig {
@Bean
public FactorybeanSample factorybeanSample() {
return new FactorybeanSample();
}
}
测试类:
import com.stu.config.SingleConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
@Test
public void testComponnet1() {
// 使用方式 加载你的入口类:即带有扫描类ComponentScan 我这里是UserConfig类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingleConfig.class);
// 当前beans名字的数组
String[] strs = applicationContext.getBeanDefinitionNames();
Object bean1 = applicationContext.getBean("factorybeanSample");
Object bean2 = applicationContext.getBean("factorybeanSample");
for (String str : strs) {
System.out.println(str);
}
System.out.println(bean1);
System.out.println(bean1 == bean2);
}
}
运行结果:
通过如上分析。我想我们可以很好干预bean的创建过程。而从而达到我们的某种需求。
2、bean的生命周期
bean创建–初始化–销毁。创建时spring容器创建的,但是我们可以自定义的就是初始化和销毁。
第一种方式:通过@Bean(initMethod = “init”,destroyMethod = “destroy”)实现。
但是我们需要注意,单实例可以关闭,但是多实例bean不会被管理。只能手动销毁。
首先我们需要先定义好一个类的初始化和销毁方法。用于后面指定调用。
package com.stu.pojo;
public class Customer {
public Customer() {
}
public void init() {
System.out.println("自定义初始化");
// 下面为业务
}
public void destroy() {
System.out.println("自定义销毁方法");
// 下面为业务
}
}
写明初始化和销毁调用方法:
package com.stu.config;
import com.stu.pojo.Customer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RecyleLifeBean {
// 自定义初始化和销毁
@Bean(initMethod = "init",destroyMethod = "destroy")
public Customer getCustomer() {
return new Customer();
}
}
测试类:
import com.stu.config.RecyleLifeBean;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
@Test
public void testComponnet1() {
// 创建IOC容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RecyleLifeBean.class);
System.out.println("IOC容器创建完毕");
// 关闭IOC容器
applicationContext.close();
}
}
第二种方式:让bean实现InitializingBean的afterPropertiesSet方法定义初始化、DisposableBean的destroy()方法销毁。
改变Customer,实现InitializingBean和DisposableBean 。
package com.stu.pojo;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Customer implements InitializingBean, DisposableBean {
public Customer() {
}
public void destroy() throws Exception {
System.out.println("执行销毁");
// 业务代码
}
public void afterPropertiesSet() throws Exception {
//业务代码
System.out.println("执行初始化");
}
}
需要扫描到我们的类。
package com.stu.config;
import com.stu.pojo.Customer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.stu.pojo")
@Configuration
public class RecyleLifeBean {
// 自定义初始化和销毁
@Bean
public Customer getCustomer() {
return new Customer();
}
}
第三种方式:使用@PostConstruct注解。这个过程是在bean创建完成并且属性设置完成后来执行初始方法。
使用注解PreDestroy,在容器销毁bean之前通知我们进行清理工作。
修改Customer,使用注解。
package com.stu.pojo;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Customer {
public Customer() {
}
@PostConstruct
public void init() {
System.out.println("对象创建赋值后,调用自定义初始化");
// 下面为业务
}
@PreDestroy
public void destroy() {
System.out.println("销毁之前,调用自定义销毁方法");
// 下面为业务
}
}
其他两个类不变,配置类可以去掉@Configuration
测试结果:同样实现了自定初始化和销毁。
第4种方式:使用实现BeanPostProcessor(bean的后置处理器),在bean初始化前进行一些处理工作。
里面有postProcessBeforeInitialization和postProcessAfterInitialization方法。
postProcessBeforeInitialization:在任何初始化之前调用。也就是上面3种方式之前
postProcessAfterInitialization:在初始化之后调用,在上面三种之后。
为了看的更加清楚上面的第一种保留:
package com.stu.pojo;
import org.springframework.stereotype.Component;
@Component
public class User {
public User() {
}
public void init() {
System.out.println("自定义初始化");
// 下面为业务
}
public void destroy() {
System.out.println("自定义销毁方法");
// 下面为业务
}
}
Customer 实现BeanPostProcessor后置处理器。
package com.stu.pojo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component // 将后置处理器加入注册
public class Customer implements BeanPostProcessor {
public Customer() {
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization....before: " + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization....after: " + beanName);
return bean;
}
}
各自实例化注入:
package com.stu.config;
import com.stu.pojo.Customer;
import com.stu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RecyleLifeBean {
// 自定义初始化和销毁
@Bean(initMethod = "init",destroyMethod = "destroy")
public User getUser() {
return new User();
}
@Bean
public Customer getCustomer() {
return new Customer();
}
}
测试类不变:
测试结果:结果符合我们上面说的过程。
上面我们可以发现。spring的bean整个生命周期我们都是可以控制的。
常见配置文件加载注解
也可以加载多个配置:PropertySources
@PropertySource(value = {"classpath:/xxx.properties"})
也可以通过环境信息拿到。
String str = applicationContext.getEnvironment().getProperty("key");
Spring依赖注入,完成自动装配。
UserController和UserService 都是加载了的bean。当我们要用这些bean的时候就需要注入。
如下为UserController,自动注入UserService的bean。
使用@Autowired自动装配:如果非必需
@Autowired(required = false)
@Controller
public class UserController {
// @Qualifier("userService1")
@Autowired
private UserService userService;
}
如果自动注入多个呢?首先默认按类名小写去找,如果不唯一,在根据属性区分是哪一个。当然你也可以通过@Qualifier指定加载哪一个
除了上面常用,也可以用@Resource。这个不是spring的时java的
package com.stu.controller;
import com.stu.service.UserService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class UserController {
@Resource
private UserService userService;
}
我经常可以看见 @Autowired加载了方法或者构造器方法上,是什么操作呢?
这是说明方法种的参数对象是来自于spring容器的对应的参数。
package com.stu.controller;
import com.stu.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
// 这个user是从容器种获取到
@Autowired
public User createUser(User user) {
return user;
}
}
Aware接口:是为了我们能够使用spring底层的组件的总接口。
ApplicationContextAware: 上下文处理
ApplicationEventPublisherAware:事件发布
BeanClassLoaderAware: 加载业务bean类时使用的类加载器暴露出来
BeanNameAware: 访问本身bean的id属性
BeanFactroyAware: 通过beanfactory来访问spring的容器
EmbeddedValueResolverAware: 加载配置
EnvironmentAware: 获取环境
ImportAware: 用来处理自定义注解的
ResourceLoaderAware: 获取资源加载器,可以获得外部资源文件
MessageSourceAware: 国际化处理相关
NotificationPublisherAware: JMX通知
ServletConfigAware:web开发过程中获取ServletConfig
ServletContextAware:web开发获取ServletContext信息
Spring提供的一个强大功能:
@Profile: 这是spring为我们提供的可以根据当前环境,动态的激活和切换一些列组件的功能。
比如:
开发环境develop、测试环境test、生产环境master
数据源:(/dev) (/test) (/master)
我们要改的只是配置文件即可。
package com.spring.config;
import java.beans.PropertyVetoException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
@Value("${db.user}")
private String user;
private String driverClass;
@Profile("default")
@Bean("test")
public DataSource testDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("dev")
@Bean("dev")
public DataSource devDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Profile("master")
@Bean("master")
public DataSource masterDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
return dataSource;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String driverClass = resolver.resolveStringValue("${db.driverClass}");
this.driverClass = driverClass;
}
}
在加载的时候,需要设置激活的环境,才会加载到对应的环境。本地调试的化可以直接该VM的参数。
package com.spring.test;
import java.util.Arrays;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.spring.config.MainConfigOfProfile;
public class IOCTestProfile {
//1. 使用命令行动态参数:在虚拟机参数位置加载 -Dspring.profiles.active=test
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
//1. 创建一个applicationContext
//2. 设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev","master");
//3. 注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4. 启动刷新容器
applicationContext.refresh();
String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
System.out.println(Arrays.toString(beanNamesForType));
applicationContext.close();
}
上面说了DI相关注解。
说说AOP的注解。在以前介绍AOP说的很详细。这里一笔带过。
最常见的例子就是AOP实现日志。
相关注解:
@Aspect::把当前类声明为切面类。
// 以下都可以设置value
@Before::把当前方法看成是前置通知。
@AfterReturning:把当前方法看成是后置通知。
@AfterThrowing:把当前方法看成是异常通知。
@After:把当前方法看成是始终通知。
@Around:把当前方法看成是环绕通知。
@Pointcut:指定切入点表达式
如果你是用使用AspectJ,还要入口处使用@EnableAspectJAutoProxy:开启对AspectJ自动代理的支持.
package com.stu.log;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LogAspectj {
// 对外提供
@Pointcut("execution(public void com.stu.pojo.User.*(..))")
public void pointcut() {}
@Before("pointcut()")
public void logStart(){
System.out.println("日志开始通知");
}
@Before("pointcut()")
public void logEnd(){
System.out.println("日志结束通知");
}
@Before("pointcut()")
public void logException(){
System.out.println("异常日志通知");
}
@Before("pointcut()")
public void logReturn(){
System.out.println("返回结果后日志通知");
}
@Before("pointcut()")
public void logRoudnd(){
System.out.println("环绕通知");
}
}
事务注解
配置好数据库,以及环境。写好对应的controller,service,mapper等。
在mapper文件已经写好。
<update id="updateUser" parameterType="com.stu.demo.pojo.User">
update t_user set username = #{userName} where usertelphone = #{userTelphone}
</update>
package com.stu.demo;
import com.stu.demo.UserService.UserService;
import com.stu.demo.pojo.User;
import com.stu.demo.web.UserController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class StushopApplicationTests {
@Autowired
private UserService userService;
@Test
public void contextLoads() {
User user = new User();
user.setUserName("zhouyi");
user.setUserTelphone("15527217650");
userService.updateUser(user);
// 正常业务,同一个方法种可以有多个地方执行sql插入,更新等操作。
// 模拟上面执行后,插入异常。
int t = 10 / 0;
}
}
运行结果:虽然报错,但是执行了更新操作。在实际种这个方法出错,应该整个方法的更新操作,都要回滚。
加上事务看看:
@Test
@Transactional
public void contextLoads() {
User user = new User();
user.setUserName("shenlong");
user.setUserTelphone("15527217650");
userService.updateUser(user);
// 正常业务,同一个方法种可以有多个地方执行sql插入,更新等操作。
// 模拟上面执行后,插入异常。
int t = 10 / 0;
}
查看结果:数据库并没有改变。这就是事务回滚的作用。
如果有心debug看,发现 int t = 10 / 0;处断点。上面的更新操作没执行。这是因为@Transactional,是整个方法没有异常错误。一起提交的。