当前位置: 首页 > news >正文

【Spring框架】Spring事务的介绍与使用方法

⚠️ 再提醒一次:Spring 本身并不实现事务,Spring事务 的本质还是底层数据库对事务的支持。你的程序是否支持事务首先取决于数据库 ,比如使用 MySQL 的话,如果你选择的是 innodb 引擎,那么恭喜你,是可以支持事务的。但是,如果你的 MySQL 数据库使用的是 myisam 引擎的话,那不好意思,Spring从根上就是不支持事务的。

这里再多提一下一个非常重要的知识点:MySQL 怎么保证原子性的?

我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚之前未完成的事务。

〇、Spring 事务简介

Spring 事务 提供一套抽象的事务管理,并且结合 Spring IoC 和 Spring AOP,简化了应用程序使用数据库事务,通过声明式事务,可以做到对应用程序无侵入的实现事务功能。例如 使用JDBC 操作数据库,想要使用事务的步骤为:

  1. 获取连接 Connection con = DriverManager.getConnection()
  2. 开启事务con.setAutoCommit(true/false);
  3. 执行CRUD
  4. 提交事务/回滚事务 con.commit() / con.rollback();
  5. 关闭连接 conn.close();

采用Spring 事务后,只需要 关注第3步的实现,其他的步骤 都是Spring 完成。

Spring事务的本质 其实就是 AOP 和 数据库事务,Spring 将数据库的事务操作提取为切面,通过AOP的方式增强事务方法。

一、Spring 支持两种方式的事务管理

Spring提供了编程式事务声明式事务两种实现方式。

1.1 编程式事务管理

Spring编程式事务有两种实现方法:1、TransactionTemplate   2、PlatformTransactionManager 

对于编程式事务管理,Spring推荐使用TransactionTemplate。

通过 TransactionTemplate或者PlatformTransactionManager   手动管理事务,实际应用中很少使用,现在大多都是用声明式事务通过Spring自动帮我们管理事务,但是编程式事务对于你理解 Spring 事务管理原理有帮助。

1.1.1 TransactionTemplate

采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate来实现事务是一样的,都属于编程式事务。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。实例代码如下:

1、无返回值

// 可以自己手动新建,也可以将TransactionTemplate交给Spring容器管理,直接通过@Autowired自动注入得到transactionTemplate对象

TransactionTemplate transactionTemplate = new TransactionTemplate();

// 如果无返回值,就new TransactionCallbackWithoutResult()

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    @Override

    protected void doInTransactionWithoutResult(TransactionStatus status) {

        try {

            // ....  业务代码

            jdbcTemplate.update("update t set v=? where k=?", "n", "k2");

            //不需要手动提交

        }catch (Exception e){

            e.printStackTrace();

            //但是需要手动回滚

            status.setRollbackOnly();

        }

    }

});

   

2、有返回值

// 新建一个TransactionTemplate

TransactionTemplate tt = new TransactionTemplate();

// 如果有返回值,就new TransactionCallback()

Object result = tt.execute(

    new TransactionCallback(){

        public Object doTransaction(TransactionStatus status){

            // 业务逻辑...

            updateOperation();

            // 返回值

            return resultOfUpdateOperation();

        }

    }

); // 执行execute方法进行事务管理

注: 使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。

使用TransactionTemplate来实现编程式事务需要自己创建事务,但是不需要自己手动提交,它如果正常执行完会自动提交事务。不过需要自己手动设置回滚,如果不设置回滚事务出现问题是不会回滚的。

1.1.2 PlatformTransactionManager  

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA,它们自己去按照接口模板实现自己的事务管理器。示例代码如下:

1、可以自己手动创建TransactionManager对象

// 定义一个某个框架平台的TransactionManager,如JDBC、Hibernate

DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();

// 设置数据源

dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource());

// 定义事务属性

DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();

// 设置传播行为属性

transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);

// 获得事务状态,开始事务

TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef);

try {

    // 业务逻辑:数据库操作

    doSomething();

    // 提交事务

    dataSourceTransactionManager.commit(status);

} catch (Exception e) {

    // 回滚回滚

    dataSourceTransactionManager.rollback(status);

}

2、也可以将TransactionManager对象交给Spring去管理,通过@Autowired自动注入获得事务管理器对象

@Autowired

private PlatformTransactionManager transactionManager;

 

public void testTransaction() {

    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

    try {

        // ....  业务代码

        transactionManager.commit(status);

    } catch (Exception e) {

        transactionManager.rollback(status);

    }

}

 

使用TransactionManager来实现编程式事务与TransactionTemplate有些许不同,它依旧需要自己创建事务,需要自己手动设置回滚。并且它还需要自己需手动提交事务,如果不手动提交,事务是不会自动提交的。

 

 

1.2 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

 

推荐使用声明式事务,因为代码侵入性最小,底层实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。声明式事务并不需要自己创建事务,提交事务,回滚等操作,Spring会自动替你完成,但是声明式事务的粒度就是方法层面的,而编程式事务的粒度可以更小

既然是基于AOP实现的,那么使用声明式事务必然需要对事务进行配置,根据不同的配置方法,可以总结出三种实现方法:1、XML实现    2、注解实现     3、混合实现(XML和注解同时使用)

其实我们能发现这三种实现方法和AOP的配置是一样的,Spring事务的底层原理就是通过AOP代理增强代码实现的,在原有代码上增加了事务管理的代码。

1.2.1 xml配置声明式事务

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:aop="http://www.springframework.org/schema/aop"

       xmlns:tx="http://www.springframework.org/schema/tx"

       xmlns:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

            http://www.springframework.org/schema/beans/spring-beans.xsd

            Index of /schema/aop

            http://www.springframework.org/schema/aop/spring-aop.xsd

            Index of /schema/tx

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

            Index of /schema/context

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

    <!-- xml 我们的业务类也配成xml -->

    <!--将业务类交给spring管理-->

    <bean id="xmlUserService" class="com.test.transaction.service.XMLUserService">

        <property name="jdbcTemplate" ref="jdbcTemplate"></property>

    </bean>

    <!--配置jdbcTemplate,来设置数据源,并将jdbcTemplate装配进Spring容器-->

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

        <property name="dataSource" ref="datasource"></property>

    </bean>

    <!--配置数据源,将数据源绑定到jdbcTemplate-->

    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <property name="username" value="root"></property>

        <property name="password" value="123456"></property>

        <property name="url" value="jdbc:mysql://localhost:3306/shadow?useSSL=false&amp;allowPublicKeyRetrieval=true&amp;serverTimezone=UTC">

        </property>

        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver">

        </property>

    </bean>

    <!--配置事务管理器,将事务管理器绑定上数据源-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="datasource"></property>

    </bean>

 

    <!--事务通知(设置在什么时候干什么事),将事务管理器作用到指定的方法中-->

    <tx:advice transaction-manager="transactionManager" id="txAdvice">

        <tx:attributes>

            <tx:method name="update"/>

        </tx:attributes>

    </tx:advice>

    <!-- aop的配置,设置切入点,表示对包路径com.test.transaction.service下的所有名为update的方法进行事务管理-->

    <aop:config>

        <aop:pointcut id="p" expression="execution(*com.test.transaction.service..*.*(..))"/>

        <aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>

    </aop:config>

</beans>

Java代码:

public class XMLUserService {

    JdbcTemplate jdbcTemplate;

    // 通过xml配置注入jdbcTemplate

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

        this.jdbcTemplate = jdbcTemplate;

    }

    // Spring会自动对update方法进行事务管理

    public void update() {

        jdbcTemplate.update("update t set v=? where k=?","n","k1");

        // 检测到抛出异常就会自动回滚事务

        throw new NullPointerException();

    }

}

1.2.2 注解配置声明式事务

JavaConfig配置类:

// 将App这个类作为JavaConfig配置类.这个注解如果不加会有问题

@Configuration

// 开启注解支撑

@EnableTransactionManagement

// 扫描这个包下面的注解

@ComponentScan("com.test.transaction")

public class App {

    // jdbcTemplate对象放到容器当中,用于连接数据库,执行sql语句

    @Bean

    public JdbcTemplate jdbcTemplate() {

        return new JdbcTemplate(dataSource());

    }

    // 将数据源对象配置到Spring容器中

    @Bean

    public DataSource dataSource() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

        dataSource.setPassword("123456");

        dataSource.setUrl("jdbc:mysql://localhost:3306/shadow?

        useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");

        dataSource.setUsername("root");

        return dataSource;

    }

    /**

    * 声明命事务管理器,将事务管理器装配到Spring容器中

    * @return

    */

    @Bean

    public PlatformTransactionManager transactionManager() {

        // 创建事务管理器对象

        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();

        // 设置数据源

        dataSourceTransactionManager.setDataSource(dataSource());

        return dataSourceTransactionManager;

    }

}

使用 @Transactional注解进行事务管理的示例代码如下:

@Transactional(propagation = Propagation.REQUIRED)

public void aMethod {

    //do something

    B b = new B();

    C c = new C();

    b.bMethod();

    c.cMethod();

}

注解@Transactional的属性

具体位置在:org.springframework.transaction.annotation.Transactional

1.2.3 混合方式

这个就没有标准,看自己的喜好哪些写到xml,哪些写成Java代码。

以下配置代码参考自Spring事务配置的五种方式。根据代理机制的不同,总结了五种Spring事务的配置方式。他们都是可以实现XML和注解混用的。

1、每个Bean都有一个代理

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

        Index of /schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd

        Index of /schema/aop

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

   

    <!-- 配置注解扫描 -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    </bean>

    <!-- 定义事务管理器(声明式的事务) -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!-- 配置DAO -->

    <bean id="userDaoTarget" class="xxx.dao.UserDaoImpl">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!-- 配置事务代理 -->

    <bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

        <!-- 配置事务管理器 -->

        <!-- 将事务管理添加到DAO层代码上 -->

        <property name="transactionManager" ref="transactionManager" />

        <property name="target" ref="userDaoTarget" />

        <property name="proxyInterfaces" value="xxx.dao.GeneratorDao" />

        <!-- 配置事务属性 -->

        <property name="transactionAttributes">

            <props>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

</beans>

2、所有Bean共享一个代理基类

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

        Index of /schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd

        Index of /schema/aop

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

   

    <!-- 配置数据源 -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    </bean>

    <!-- 定义事务管理器(声明式的事务) -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

   

    <!-- 配置事务基类 -->

    <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true">

        <!-- 配置事务管理器 -->

        <property name="transactionManager" ref="transactionManager" />

        <!-- 配置事务属性 -->

        <property name="transactionAttributes">

            <props>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

    <!-- 配置DAO -->

    <bean id="userDaoTarget" class="xxx.dao.UserDaoImpl">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <bean id="userDao" parent="transactionBase" >

        <property name="target" ref="userDaoTarget" />

    </bean>

</beans>

3、使用拦截器

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

        Index of /schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd

        Index of /schema/aop

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

    <!-- 配置数据源 -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    </bean>

    <!-- 定义事务管理器(声明式的事务) -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!-- 配置事务拦截器 -->

    <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">

        <property name="transactionManager" ref="transactionManager" />

        <!-- 配置事务属性 -->

        <property name="transactionAttributes">

            <props>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

   

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

        <property name="beanNames">

            <list>

                <value>*Dao</value>

            </list>

        </property>

        <property name="interceptorNames">

            <list>

                <value>transactionInterceptor</value>

            </list>

        </property>

    </bean>

    <!-- 配置DAO -->

    <bean id="userDao" class="xxx.dao.UserDaoImpl">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

</beans>

 

 

4、使用tx标签配置的拦截器

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

        Index of /schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd

        Index of /schema/aop

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

   

    <!-- 开启注解支持 -->

    <context:annotation-config />

    <!-- 扫描注解 现在可以不写上面那句,直接写下面的扫描注解标签就可以开启注解支持了-->

    <context:component-scan base-package="xxx" />

    <!-- 定义数据源 -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    </bean>

    <!-- 定义事务管理器(声明式的事务) -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!-- 定义事务通知 -->

    <tx:advice id="txAdvice" transaction-manager="transactionManager">

        <tx:attributes>

            <tx:method name="*" propagation="REQUIRED" />

        </tx:attributes>

    </tx:advice>

    <!-- 定义切面 -->

    <aop:config>

        <aop:pointcut id="interceptorPointCuts" expression="execution(* xxx.dao.*.*(..))" />

        <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" />

    </aop:config>

</beans>

5、全注解

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

        Index of /schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd

        Index of /schema/aop

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

       

    <!-- 1.配置数据源 -->

    <context:annotation-config />

    <!-- 2.配置事务管理器 -->

    <context:component-scan base-package="xxx" />

    <!-- 3.配置事务管理器 -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    </bean>

   

    <!-- 定义事务管理器(声明式的事务) -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!-- 启用注解方式的声明式事务,如果将这个去掉,将不会创建事务 -->

    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

然后在需要使用事务的类或方法上加上注解@Transactional即可。

1.3 编程式事务和声明式事务的区别

编程式事务每次实现都要单独实现,业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

二、Spring 事务管理接口介绍

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。
  • TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus:事务运行状态。

我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

2.1 PlatformTransactionManager事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是:PlatformTransactionManager,它是Spring 事务结构中的核心接口。

通过这个接口,Spring 为各个平台如:JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

PlatformTransactionManager 接口的具体实现如下:

PlatformTransactionManager接口中定义了三个方法:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {

    // 获得事务

    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    // 提交事务

    void commit(TransactionStatus var1) throws TransactionException;

    // 回滚事务

    void rollback(TransactionStatus var1) throws TransactionException;

}

 

通过PlatformTransactionManager的接口 可以看出,Spring 事务的 的三个核心方法,事务开启 ,提交事务,回滚事务,Spring 事务功能的实现 都是围绕这三个方法来实现。

 

 

 

这里多插一嘴。为什么要定义或者说抽象出来PlatformTransactionManager这个接口呢?

主要是因为要将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。

“为什么我们要用接口?” 

《设计模式》(GOF 那本)这本书在很多年前都提到过说要基于接口而非实现编程,你真的知道为什么要基于接口编程么?

纵观开源框架和项目的源码,接口是它们不可或缺的重要组成部分。要理解为什么要用接口,首先要搞懂接口提供了什么功能。我们可以把接口理解为提供了一系列功能列表的约定,接口本身不提供功能,它只定义行为。但是谁要用,就要先实现我,遵守我的约定,然后再自己去实现我定义的要实现的功能。

举个例子,我上个项目有发送短信的需求,为此,我们定了一个接口,接口只有两个方法:

1.发送短信 2.处理发送结果的方法。

刚开始我们用的是阿里云短信服务,然后我们实现这个接口完成了一个阿里云短信的服务。后来,我们突然又换到了别的短信服务平台,我们这个时候只需要再实现这个接口即可。这样保证了我们提供给外部的行为不变。几乎不需要改变什么代码,我们就轻松完成了需求的转变,提高了代码的灵活性和可扩展性。

什么时候用接口?当你要实现的功能模块设计抽象行为的时候,比如发送短信的服务,图床的存储服务等等。

2.1.1 JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource" />

</bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2.1.2 Hibernate事务

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

    <property name="sessionFactory" ref="sessionFactory" />

</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

2.1.3 Java持久化API事务(JPA

Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">

    <property name="sessionFactory" ref="sessionFactory" />

</bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

2.1.4 Java原生API事务

如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">

    <property name="transactionManagerName" value="java:/TransactionManager" />

</bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

2.2 TransactionInfo:事务信息

事务信息对象,包括一个事务所有的信息,持有事务管理器、事务定义对象、目标方法唯一标识、事务状态对象、外层的TransactionInfo,外层的TransactionInfo 用于在应用程序中获取 当前的TransactionInfo 对象。

2.3 TransactionDefinition事务定义

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。

什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。

事务属性包含了 5 个方面:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = 1;

    int ISOLATION_READ_COMMITTED = 2;

    int ISOLATION_REPEATABLE_READ = 4;

    int ISOLATION_SERIALIZABLE = 8;

    int TIMEOUT_DEFAULT = -1;

 

    // 返回事务的传播行为,默认值为 REQUIRED

    int getPropagationBehavior();

    // 返回事务的隔离级别,默认值是 DEFAULT

    int getIsolationLevel();

    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

    int getTimeout();

    // 返回是否为只读事务,默认值为 false

    boolean isReadOnly();

 

    @Nullable

    String getName();

}

2.4 TransactionStatus事务状态

TransactionStatus接口用来记录事务的状态。该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。

TransactionStatus 接口内容如下:

public interface TransactionStatus{

    boolean isNewTransaction(); // 是否是新的事务

    boolean hasSavepoint(); // 是否有恢复点

    void setRollbackOnly();  // 设置为只回滚

    boolean isRollbackOnly(); // 是否为只回滚

    boolean isCompleted; // 是否已完成

}

 

 

2.5 TransationSynchronization事务同步回调接口

事务同步回调接口,在事务 周期的各个点 执行回调 方法。比如 挂起 ,继续,提交前后 ,完成前后 。用于 管理 应用程序在事务周期中绑定的资源。在Spring - Mybatis 整合时,正式Mybatis 正式利用了TransationSynchronization同步器,才让Mybatis 的事务管理交给了 Spring 事务来管理。

2.6 TransactionSynchronizationManager事务同步回调的管理器

在事务运行过程中,需要保存一些状态,比如 数据库连接,

ThreadLocal<Map<Object, Object>> resources - 应用代码随事务的声明周期绑定的对象  

ThreadLocal<Set<TransactionSynchronization>> synchronizations-使用的同步器,用于应用扩展  

ThreadLocal<String> currentTransactionName-事务的名称  

ThreadLocal<Boolean> currentTransactionReadOnly-事务是否是只读  

ThreadLocal<Integer> currentTransactionIsolationLevel-事务的隔离级别  

ThreadLocal<Boolean> actualTransactionActive-是否实际的开启了事务,如果加入 到 别的事务,就不是实际开启事务。

2.7 SuspendedResourceHolder挂起的资源持有对象

在挂起一个事务时,用于记录被挂起事务的运行时信息,这些信息就是TransactionSynchronizationManager中记录的事务信息。然后将这些信息 保存在 新的DefaultTransactionStatus对象中,便于内部事务运行结束后,恢复外层事务。

 

 

三、事务属性详解

实际业务开发中,大家一般都是使用 @Transactional 注解来开启事务,很多人并不清楚这个参数里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。

3.1 事务传播行为(事务传播机制)

事务传播行为是为了解决业务层方法之间互相调用的事务问题。事务的传播性一般用在事务嵌套 的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。

@Service

Class A {

    @Autowired

    B b;

   

    @Transactional(propagation = Propagation.xxx)

    public void aMethod {

        //do something

        b.bMethod();

    }

}

 

@Service

Class B {

    @Transactional(propagation = Propagation.xxx)

    public void bMethod {

       //do something

    }

}

在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    ......

}

不过,为了方便使用,Spring 相应地定义了一个枚举类:Propagation

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

public enum Propagation {

    //如果当前存在事务则加入该事务 如果当前没有事务,则创建一个新的事务

    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)

    //如果当前存在事务则加入该事务 如果当前没有事务 则以非事务的方式继续运行

    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)

    //如果当前存在事务则加入该事务 如果当前没有事务 则抛出异常

    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY)

    //创建一个新的事务 如果当前存在事务 则把当前事务挂起

    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)

    //以非事务方式运行 如果当前存在事务 则把当前事务挂起

    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED)

    //以非事务方式运行如果当前存在事务则抛出异常

    NEVER(TransactionDefinition.PROPAGATION_NEVER)

    //如果当前存在事务 则创建一个事务作为当前事务的嵌套事务来运行 如果不存在则创建一个新的事务

    NESTED(TransactionDefinition.PROPAGATION_NESTED);

 

    private final int value;

 

    Propagation(int value) {

        this.value = value;

    }

 

    public int value() {

        return this.value;

    }

}

正确的事务传播行为可能的值如下:

1.TransactionDefinition.PROPAGATION_REQUIRED

REQUIRED 是 Spring 默认的事务传播类型,该传播类型的特点是:当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。

它是使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 如果外部方法开启事务并且被Propagation.REQUIRED修饰的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

举个例子:如果我们上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。

@Service

Class A {

    @Autowired

    B b;

    @Transactional(propagation = Propagation.REQUIRED)

    public void aMethod {

        //do something

        b.bMethod();

    }

}

 

@Service

Class B {

    @Transactional(propagation = Propagation.REQUIRED)

    public void bMethod {

       //do something

    }

}

为了验证 REQUIRED 事务传播类型的特点,我们再来做几个测试。

还是上面 methodA 和 methodB 的例子。当 methodA 不开启事务,methodB 开启事务,这时候 methodB 就是独立的事务,而 methodA 并不在事务之中。因此当 methodB 发生异常回滚时,methodA 中的内容就不会被回滚。用如下的代码就可以验证我们所说的。

public void methodA(){

    System.out.println("methodA");

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

}

@Transactional

public void methodB(){

    System.out.println("methodB");

    tableService.insertTableB(new TableEntity());

    throw new RuntimeException();

}

最终的结果是:tablea 插入了数据,tableb 没有插入数据,符合了我们的猜想。

当 methodA 开启事务,methodB 也开启事务。按照我们的结论,此时 methodB 会加入 methodA 的事务。此时,我们验证当父子事务分别回滚时,另外一个事务是否会回滚。

我们先验证第一个:当父方法事务回滚时,子方法事务是否会回滚?

@Transactional

public void methodA(){

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

    throw new RuntimeException();

}

@Transactional

public void methodB(){

    tableService.insertTableB(new TableEntity());

}

结果是:talbea 和 tableb 都没有插入数据,即:父事务回滚时,子事务也回滚了。

我们继续验证第二个:当子方法事务回滚时,父方法事务是否会回滚?

@Transactional

public void methodA(){

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

}

@Transactional

public void methodB(){

    tableService.insertTableB(new TableEntity());

    throw new RuntimeException();

}

结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。

我们继续验证第三个:当字方法事务回滚时,父方法捕捉了异常,父方法事务是否会回滚?

@Transactional

public void methodA() {

    tableService.insertTableA(new TableEntity());

    try {

        transactionServiceB.methodB();

    } catch (Exception e) {

        System.out.println("methodb occur exp.");

    }

}

   

@Transactional

public void methodB() {

    tableService.insertTableB(new TableEntity());

    throw new RuntimeException();

}

结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。所以说,这也进一步验证了我们之前所说的:REQUIRED 传播类型,它是父子方法共用同一个事务的。

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

REQUIRES_NEW 也是常用的一个传播类型,该传播类型的特点是:无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

举个例子:如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常(如果在bMethod方法内try...catch处理了这个异常,那么aMethod就不会检测到这个异常,也就不会回滚了)并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。

@Service

Class A {

    @Autowired

    B b;

    @Transactional(propagation = Propagation.REQUIRED)

    public void aMethod {

        //do something

        b.bMethod();

    }

}

@Service

Class B {

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void bMethod {

       //do something

    }

}

 

 

为了验证 REQUIRES_NEW 事务传播类型的特点,我们再来做几个测试。

首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?

@Transactional

public void methodA(){

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

    throw new RuntimeException();

}

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void methodB(){

    tableService.insertTableB(new TableEntity());

}

结果是:tablea 没有插入数据,tableb 插入了数据,即:父方法事务回滚了,但子方法事务没回滚。这可以证明父子方法的事务是独立的,不相互影响。

下面,我们来看看:当子方法事务发生异常时,父方法事务是否会回滚?

@Transactional

public void methodA(){

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

}

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void methodB(){

    tableService.insertTableB(new TableEntity());

    throw new RuntimeException();

}

结果是:tablea 没有插入了数据,tableb 没有插入数据。

从这个结果来看,貌似是子方法事务回滚,导致父方法事务也回滚了。但我们不是说父子事务都是独立的,不会相互影响么?怎么结果与此相反呢?

其实是因为子方法抛出了异常,而父方法并没有做异常捕捉,此时父方法同时也抛出异常了,于是 Spring 就会将父方法事务也回滚了。如果我们在父方法中捕捉异常,那么父方法的事务就不会回滚了,修改之后的代码如下所示。

@Transactional

public void methodA(){

    tableService.insertTableA(new TableEntity());

    // 捕捉异常

    try {

        transactionServiceB.methodB();

    } catch (Exception e) {

        e.printStackTrace();

    }

}

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void methodB(){

    tableService.insertTableB(new TableEntity());

    throw new RuntimeException();

}

结果是:tablea 插入了数据,tableb 没有插入数据。这正符合我们刚刚所说的:父子事务是独立的,并不会相互影响。

这其实就是我们上面所说的:父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。因为如果执行过程中发生 RuntimeException 异常和 Error 的话,那么 Spring 事务是会自动回滚的。

 

3.TransactionDefinition.PROPAGATION_NESTED

NESTED 也是常用的一个传播类型,该方法的特性与 REQUIRED 非常相似,其特性是:当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。

 

可以看到 NESTED 与 REQUIRED 的区别在于:父方法与子方法对于共用事务的描述是不一样的,REQUIRED 说的是共用同一个事务,而 NESTED 说的是在嵌套事务执行。这一个区别的具体体现是:在子方法事务发生异常回滚时,父方法有着不同的反应动作。

 

对于 REQUIRED 来说,无论父子方法哪个发生异常,全都会回滚。而 REQUIRED 则是:父方法发生异常回滚时,子方法事务会回滚。而子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。

如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。也就是说:

  • 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
  • 如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。

这里还是简单举个例子:如果 bMethod() 回滚的话,aMethod()不会回滚(内部事务回滚,外部事务不回滚)。如果 aMethod() 回滚的话,bMethod()会回滚(外部事务回滚,内部事务也回滚)。

@Service

Class A {

    @Autowired

    B b;

 

    @Transactional(propagation = Propagation.REQUIRED)

    public void aMethod {

        //do something

        b.bMethod();

    }

}

 

@Service

Class B {

    @Transactional(propagation = Propagation.NESTED)

    public void bMethod {

       //do something

    }

}

 

 

为了验证 NESTED 事务传播类型的特点,我们再来做几个测试。

首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?

@TransactionalpublicvoidmethodA() {

    tableService.insertTableA(newTableEntity());

    transactionServiceB.methodB();

    thrownewRuntimeException();

}

@Transactional(propagation = Propagation.NESTED)

publicvoidmethodB() {

    tableService.insertTableB(newTableEntity());

}

结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明父方法发送异常时,子方法事务会回滚。

接着,我们继续验证一下:当子方法事务发生异常时,如果父方法没有捕捉异常,父方法事务是否会回滚?

@TransactionalpublicvoidmethodA() {

    tableService.insertTableA(newTableEntity());

    transactionServiceB.methodB();

}

@Transactional(propagation = Propagation.NESTED)

publicvoidmethodB() {

    tableService.insertTableB(newTableEntity());

    thrownewRuntimeException();

}

结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明子方法发送异常回滚时,如果父方法没有捕捉异常(try...catch异常),那么父方法事务也会回滚。

最后,我们验证一下:当子方法事务发生异常时,如果父方法捕捉了异常,父方法事务是否会回滚?

@TransactionalpublicvoidmethodA() {

    tableService.insertTableA(newTableEntity());

    try{

        transactionServiceB.methodB();

    } catch(Exceptione) {

       

    }

}

@Transactional(propagation = Propagation.NESTED)

publicvoidmethodB() {

    tableService.insertTableB(newTableEntity());

    thrownewRuntimeException();

}

结果是:tablea 插入了数据,tableb 没有插入数据,即:父方法事务没有回滚,子方法事务回滚了。这说明子方法发送异常回滚时,如果父方法捕捉了异常,那么父方法事务就不会回滚。

看到这里,相信大家已经对 REQUIRED、REQUIRES_NEW 和 NESTED 这三个最常用的事务传播类型有了深入的理解了。最后,让我们来总结一下:

事务传播类型

特性

REQUIRED

当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。

REQUIRES_NEW

无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。

NESTED

当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。

 

4.TransactionDefinition.PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

这个使用的很少,就不举例子来说了。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

 

3.2 事务隔离级别

事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。

在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:

  • 脏读(Dirty read)

脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。

  • 不可重复读(Nonrepeatable read)

不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。不可重复读重点在修改。

  • 幻读(Phantom reads)

幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。幻读重点在新增或删除。

在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。

 

 

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

public interface TransactionDefinition {

    ......

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = 1;

    int ISOLATION_READ_COMMITTED = 2;

    int ISOLATION_REPEATABLE_READ = 4;

    int ISOLATION_SERIALIZABLE = 8;

    ......

}

和事务传播行为那块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation

public enum Isolation {

    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

    // 最低级别,只能保证不读取 物理上损害的数据,允许脏读

    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

    // 只能读到已经提交的数据

    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

    // 可重复读

    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

    // 串行化读,读写相互阻塞

    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

 

    private final int value;

 

    Isolation(int value) {

      this.value = value;

    }

 

    public int value() {

      return this.value;

    }

 

}

 

 

下面我依次对每一种事务隔离级别进行介绍:

  • TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务的隔离级别示例:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

读取未提交数据(会出现脏读, 不可重复读) 基本不使用

3.3 事务超时属性

为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的一个特性就是它的超时。

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。

假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

事务的超时性示例:

@Transactional(timeout=30)

3.4 事务只读属性

如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {

    ......

    // 返回是否为只读事务,默认值为 false

    boolean isReadOnly();

}

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

很多人就会疑问了,为什么我一个数据查询操作还要启用事务支持呢?

拿 MySQL 的 innodb 举例子,根据官网 MySQL :: MySQL 5.7 Reference Manual :: 14.7.2.2 autocommit, Commit, and Rollbackopen in new window描述:MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。

但是,如果你给方法加上了Transactional注解的话,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。

如果不加Transactional,每条sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。

分享一下关于事务只读属性,其他人的解答:

  • 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
  • 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持

只读示例:

@Transactional(readOnly=true)

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。

3.5 事务回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行时异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常何IO异常(IOException)时不会回滚

例如下面的代码执行后,tablea 和 tableb 两个表格,都会插入一条数据,它们是不会回滚的:

@Transactional

public void methodA() throws Exception {

    tableService.insertTableA(new TableEntity());

    transactionServiceB.methodB();

}

 

@Transactional

public void methodB() throws Exception {

    tableService.insertTableB(new TableEntity());

    // RuntimeException

    throw new Exception();

}

回滚示例:

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。

如果你想要回滚你定义的特定的异常类型的话,可以通过注解设置:

@Transactional(rollbackFor= MyException.class)

 

也可以通过XML设置:

<tx:method name="update" propagation="REQUIRED"/>

 

 

 

但是比如设置了IO异常就回滚,如果在代码中try...catch处理了这个异常之后,就不会回滚了。

 

 

四、Spring事务使用方法论

看完了事务的传播类型,我们对 Spring 事务又有了深刻的理解。

看到这里,你应该也明白:使用事务,不再是简单地使用 @Transaction 注解就可以,还需要根据业务场景,选择合适的传播类型。那么我们再升华一下使用 Spring 事务的方法论。一般来说,使用 Spring 事务的步骤为:

  1. 根据业务场景,分析要达成的事务效果,确定使用的事务传播类型。
  2. 在 Service 层使用 @Transaction 注解,配置对应的 propogation 属性。(事务管理一般是对Service层使用的,所以以后编写Service层时一定不要忘了加事务管理,事务管理是系统开发的最基本素质,如果一个程序员在开发的时候不去对Service层做事务管理,那么这个程序员就该被开除了

下次遇到要使用事务的情况,记得按照这样的步骤去做哦~

@Transactional 介绍

@Transactional 注解 可以作用于接口、接口方法、类以及类方法上当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义

虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常

相关文章:

【Spring框架】Spring事务的介绍与使用方法

⚠️ 再提醒一次&#xff1a;Spring 本身并不实现事务&#xff0c;Spring事务 的本质还是底层数据库对事务的支持。你的程序是否支持事务首先取决于数据库 &#xff0c;比如使用 MySQL 的话&#xff0c;如果你选择的是 innodb 引擎&#xff0c;那么恭喜你&#xff0c;是可以支持…...

七夕特别篇 | 浪漫的Bug

文章目录 前言一、迷失的爱情漩涡&#xff08;多线程中的错误同步&#xff09;1.1 Bug 背景1.2 Bug 分析1.3 Bug 解决 二、心形积分之恋&#xff08;心形面积计算中的数值积分误差&#xff09;1.1 Bug 背景1.1.1 背景1.1.2 数学模型 1.2 Bug 分析1.2.1 初始代码1.2.2 代码工作流…...

数据结构双向链表

Hello&#xff0c;好久不见&#xff0c;今天我们讲链表的双向链表&#xff0c;这是一个很厉害的链表&#xff0c;带头双向且循环&#xff0c;学了这个链表&#xff0c;你会发现顺序表的头插头删不再是一个麻烦问题&#xff0c;单链表的尾插尾删也变得简单起来了&#xff0c;那废…...

解决政务审计大数据传输难题!镭速传输为政务行业提供解决方案

政务行业是国家治理的重要组成部分&#xff0c;涉及到国家安全、社会稳定、民生福祉等方面。随着信息技术的快速发展和革新&#xff0c;政务信息化也迎来了新一轮的升级浪潮。国家相继出台了《国家信息化发展战略纲要》《“十三五”国家信息化规划》《“十四五”推进国家政务信…...

redis 7高级篇1 redis的单线程与多线程

一 redis单线程与多线程 1.1 redis单线程&多线程 1.redis的单线程 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理…...

GO语言:Worker Pools线程池、Select语句、Metex互斥锁详细示例教程

目录标题 一、Buffered Channels and Worker Pools1. Goroutine and Channel Example 线程和通道示例2. Deadlock 死锁3. Closing buffered channels 关闭通道4. Length vs Capacity 长度和容量5. WaitGroup6. Worker Pool Implementation 线程池 二、Select1. Example2. Defau…...

vue ui 创建项目没有反应

问题 cmd中输入 vue ui 没有反应 解决办法 vue ui命令需要vue3.0以上的版本才可以 1、查看当前版本 vue --version vue版本在3.0以下是没有ui命令的 2、查看版本所拥有的命令 vue -h 3、卸载之前版本的vue npm uninstall vue-cli -g 卸载完成&#xff0c;检查是否已经…...

go语言中channel类型

目录 一、什么是channel 二、为什么要有channel 三、channel操作使用 初始化 操作 单向channel 双向channel&#xff0c;可读可写 四、close下什么场景会出现panic 五、总结 一、什么是channel Channels are a typed conduit through which you can send and receive …...

基于STM32F1的电子罗盘HMC5883L角度测量

基于STM32F1的电子罗盘HMC5883L角度测量 参考 1. HMC5883L模块 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Axqqv48y-1692885921487)(…\img\HMC5883L.png)] 型号&#xff1a;GY-271使用芯片&#xff1a;HMCL5883L供电电源&#xff1a;3-5V通…...

Oracle解锁表、包、用户、杀会话、停job

Oracle解锁表、包、用户、杀会话、停job 一、创建包tzq_server_pkg二、授权给需要使用的用户log三、解锁表&#xff1a;执行存过unlock_table(schema_name, table_name)四、解锁包&#xff1a;执行存过unlock_package(schema_name, pkg_name)五、解锁用户&#xff1a;执行存过u…...

软考高级系统架构设计师系列论文九十九:论软件开发平台的选择和应用

软考高级系统架构设计师系列论文九十九:论软件开发平台的选择和应用 一、相关知识点二、摘要三、正文四、总结一、相关知识点 软考高级系统架构设计师系列之:面向构件的软件设计,构件平台与典型架构二、摘要 本文从一个行业MIS系统的开发实践,讨论了软件开发平台的选择和应…...

Redis Pub/Sub 指南

Redis 不仅仅是一个数据库&#xff0c;还可以作为支持发布和订阅&#xff08;Pub/Sub&#xff09;操作的消息代理。本文将使用 Navicat for Redis 简要概述 Redis 的 Pub/Sub 功能。 关于发布或订阅消息范式 Pub/Sub 是一种模式&#xff0c;发送者&#xff08;广播者&#xf…...

Nest(2):Nest 应用目录结构和脚手架命令介绍

Nest 应用目录结构和脚手架命令介绍 在正式使用 NestJS 进行开发之前&#xff0c;先来了解下 Nest 应用的目录结构&#xff0c;和一些常用的脚本命令。 工程目录 下面是使用 nest/cli 创建的 Nest 项目的目录结构。 上篇文章中介绍了 src 目录以及目录下各个文件的作用。下面…...

【嵌入式】MKV31F512VLL12 微控制器 (MCU) 、Cyclone® IV E EP4CE10E22I8LN,FPGA-现场可编程门阵列芯片

1、MKV31F512VLL12 微控制器 (MCU) 是适用于BLDC、PMSM和ACIM电机控制应用的高性能解决方案。这些MCU采用运行频率为100MHz/120MHz、带数字信号处理 (DSP) 和浮点单元 (FPU) 的ARM Cortex-M4内核。KV3x MCU配备两个采样率高达1.2MS/s的16位ADC、多个控制定时器以及512KB闪存。 …...

矢量调制分析基础

前言 本文介绍VSA 的矢量调制分析和数字调制分析测量能力。某些扫频调谐频谱分析仪也能通过使用另外的数字无线专用软件来提供数字调制分析。然而&#xff0c;VSA 通常在调制格式和解调算法配置等方面提供更大的测量灵活性&#xff0c;并提供更多的数据结果和轨迹轨迹显示。本…...

ensp-Ipv6配置配置

ensp-Ipv6配置配置 &#x1f4ce;ipv6.zip&#x1f4ce;Ipv6 网络.docx...

java八股文面试[java基础]—— hashCode 与 equals 区别 == 与 equals的区别

两个对象的hashCode()相同时&#xff0c;equals()相等吗&#xff1f;_两个对象的hashcode一样,equal一样么_不想当个程序员的博客-CSDN博客 equals()&#xff1a;比较的是非基本类型的数据的引用地址&#xff08;即内存地址&#xff09;是否相同&#xff0c;但是对于重写equal…...

Dubbo之PojoUtils源码分析

功能概述 PojoUtils是一个工具类&#xff0c;能够进行深度遍历&#xff0c;将简单类型与复杂类型的对象进行转换&#xff0c;在泛化调用时用到&#xff08;在泛化调用中&#xff0c;主要将Pojo对象与Map对象进行相互转换&#xff09; 功能分析 核心类PojoUtils分析 主要成员…...

【C++】—— C++11新特性之 “右值引用和移动语义”

前言&#xff1a; 本期&#xff0c;我们将要的介绍有关 C右值引用 的相关知识。对于本期知识内容&#xff0c;大家是必须要能够掌握的&#xff0c;在面试中是属于重点考察对象。 目录 &#xff08;一&#xff09;左值引用和右值引用 1、什么是左值&#xff1f;什么是左值引用…...

谈一谈redis脑裂

什么是redis脑裂 &#xff08;1&#xff09;一主多从架构中&#xff0c;主节点与客户端通信正常&#xff0c;主节点与哨兵、从节点连接异常&#xff0c;客户端仍正常写入数据 &#xff08;2&#xff09;哨兵判定主节点下线&#xff0c;重新选主 &#xff08;3&#xff09;原主…...

基于原生Servlet使用模板引擎Thymeleaf访问界面

我们常在Spring Boot项目中使用Thymeleaf模板引擎,今天突发奇想&#xff0c;尝试原生Servlet访问&#xff01; 说做就做 搭建完整的WEB项目 其中的大部分依赖都是后续报错 追加进来的 导入依赖 thymeleaf-3.0.11.RELEASE.jar 第一次访问 访问地址: http://localhost:8080…...

【C语言】15-函数-1

1. 初步认识函数 通过前几章的学习,已经可以编写一些简单的 C 语言程序了,但是如果程序的功能比较多,规模比较大,把所有的程序代码都写在一个主函数(main函数)中,就会使主函数变得庞杂、头绪不清,使阅读和维护程序变得困难。此外,有时程序中要多次实现某一功能就需要…...

08-信息收集-架构、搭建、WAF等

信息收集-架构、搭建、WAF等 信息收集-架构、搭建、WAF等一、前言说明二、CMS识别技术三、源码获取技术四、架构信息获取技术五、站点搭建分析1、搭建习惯-目录型站点2、搭建习惯-端口类站点3、搭建习惯-子域名站点4、搭建习惯-类似域名站点5、搭建习惯-旁注&#xff0c;c段站点…...

Qt --- 显示相关设置 窗口属性等

主界面&#xff0c;窗口 最小化 最大化 关闭按钮、显示状态自定义&#xff1a; setWindowFlags(Qt::CustomizeWindowHint); setWindowFlags(Qt::WindowCloseButtonHint); //只要关闭按钮 setWindowFlags(Qt::WindowFlags type) Qt::FrameWindowHint:没有边框的窗口 Qt::Window…...

使用小程序实现左侧菜单,右侧列表双向联动效果

目录 引言理解双向联动效果的重要性scrollview属性介绍实现左侧菜单数据准备渲染菜单列表监听菜单点击事件实现右侧列表数据结构设计初始数据渲染监听列表滚动事件左侧菜单与右侧列表联动获取当前滚动位置计算对应菜单项联动效果优化用户体验考虑平滑滚动效果菜单高亮状态...

selenium中处理验证码问题

验证码 基本作用&#xff1a;可以实现当前访问页面的数据安全性、还可以减少用户的并发数&#xff1b; 类型&#xff1a;1、纯数字、纯字母&#xff1b;2、汉字组合&#xff1b;3、数学运算题&#xff1b;4、滑动&#xff1b;5、图片&#xff08;选不同的、选相同、成语顺序&…...

EMR电子病历系统 SaaS电子病历编辑器源码 电子病历模板编辑器

EMR&#xff08;Electronic Medical Record&#xff09;指的是电子病历。它是一种基于电子文档的个人医疗记录&#xff0c;可以包括病人的病史、诊断、治疗方案、药物处方、检查报告和护理计划等信息。EMR采用计算机化的方式来存储、管理和共享这些信息&#xff0c;以便医生和医…...

一些自定义hooks

文章目录 1、点击框外隐藏弹窗hook 1、点击框外隐藏弹窗hook **描述&#xff1a;**有一个需要自己封装弹窗的组件&#xff0c;实现点击弹窗框外时隐藏弹窗 代码&#xff1a; import { useEffect } from “react”; // 点击框外hooks import { useEffect } from "react&q…...

基于Citespace、vosviewer、R语言的文献计量学可视化分析技术及全流程文献可视化SCI论文高效写作方法

文献计量学是指用数学和统计学的方法&#xff0c;定量地分析一切知识载体的交叉科学。它是集数学、统计学、文献学为一体&#xff0c;注重量化的综合性知识体系。特别是&#xff0c;信息可视化技术手段和方法的运用&#xff0c;可直观的展示主题的研究发展历程、研究现状、研究…...

lEC 61068-2-14_2023环境试验.第2-14部分:试验.试验N:温度变化, 最新版发布

https://download.csdn.net/download/m0_67373485/88251313 lEC 61068-2-14_2023环境试验.第2-14部分:试验.试验N:温度变化 A change of temperature test is intended to determine the effect on the specimen of a changeof temperature or a succession of changes of tem…...

CFDEM学习笔记

本文用来记录自己学习CFDEM的笔记。 资料总结 虚拟机&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1MPMTJQfl76mW0H5bbT_rAg 提取码&#xff1a;rqli 开机密码&#xff1a;530944988 知乎博客&#xff1a;作者说明了如何关闭颗粒碰撞计算来达到提升计算速度。 Githu…...

SpringBoot入门篇1 - 简介和工程创建

目录 SpringBoot是由Pivotal团队提供的全新框架&#xff0c; 其设计目的是用来简化Spring应用的初始搭建以及开发过程。 1.创建入门工程案例 ①创建新模块&#xff0c;选择Spring初始化&#xff0c;并配置模块相关基础信息 ②开发控制器类 controller/BookController.jav…...

MyBatis-Plus updateById不更新null值

文章目录 前言方式一 调整全局的验证策略方式二 调整字段验证注解方式三 使用 UpdateWrapper 前言 在 MyBatis-Plus 中&#xff0c;使用updateById&#xff0c;null字段并不会更新&#xff0c;其实是和更新的策略有关&#xff0c;当然&#xff0c;也有插入策略&#xff0c;本文…...

用pytorch实现AlexNet

AlexNet经典网络由Alex Krizhevsky、Hinton等人在2012年提出&#xff0c;发表在NIPS&#xff0c;论文名为《ImageNet Classification with Deep Convolutional Neural Networks》&#xff0c;论文见&#xff1a;http://www.cs.toronto.edu/~hinton/absps/imagenet.pdf &#xf…...

LeetCode560.和为k的子数组

这道题我用的是暴力法&#xff0c;当然也是不断的提交不断发现问题改出来的&#xff0c;比如我之前是算到和大于目标值就break&#xff0c;其实不行因为后面还可以有负数&#xff0c;我把break删了。后面和为目标之后就答案1然后break然后下一次遍历&#xff0c;测试用例中就出…...

echarts 的dataZoom滑块两端文字被遮挡

问题&#xff1a; 期望&#xff1a; 解决方案&#xff1a; 1&#xff1a;调整宽度&#xff08;4版本的没有width属性&#xff09; 2. 参考&#xff1a;echarts图标设置dataZoom拖拽时间轴时自动调整两侧文字的位置_datazoom 位置_乌栖曲的博客-CSDN博客 设置文字的定位 cons…...

MongoDB基本使用

在 MongoDB 中我们可以使用use命令来创建数据库&#xff0c;如果该数据库不存在&#xff0c;则会创建一个新的数据库&#xff0c;如果该数据库已经存在&#xff0c;则将切换到该数据库。使用use命令创建数据库的语法格式如下&#xff1a; --use database_name use my_db1;数据…...

C++ 中的左值(Lvalues)和右值(Rvalues)

C 中有两种类型的表达式&#xff1a; 左值&#xff08;lvalue&#xff09;&#xff1a;左值参数是可被引用的数据对象&#xff0c;例如&#xff0c;变量、数组元素、结构成员、引用和解除引用的指针都是左值。非左值包括字面常量&#xff08;用引号起的字符串除外&#xff0c;…...

html流光按钮

出处bilibili猫咪爱狗 <!DOCTYPE html> <html><head><style>body {/*内容居中&#xff0c;背景色*/height: 100vh;display: flex;justify-content: center; align-items: center;background-color: #000;}a { /*水平垂直居中*/position: re…...

HAProxy+nginx搭建负载均衡群集

目录 一、常见的Web集群调度器 二、HAProxy群集介绍 1、Haproxy的特性 : 2、Haproxy常用的调度算法 ① 轮询调度&#xff08;Round Robin&#xff09; ② 最小连接数&#xff08;Least Connections&#xff09; ③ 基于来源访问调度算法&#xff08;Source Hashing&am…...

logback-spring.xml 的配置及详解(直接复制粘贴可用)

logback-spring.xml 的配置及详解 一、注意实现二、配置及详解 一、注意实现 logback-spring.xml 中有三处需要根据实际业务进行修改&#xff0c;直接查找“&#xff08;根据业务修改&#xff09;”即可进行定位。 如果不想修改&#xff0c;直接复制粘贴到自己系统运行也可以&…...

C语言易错点整理

前言&#xff1a; 本文涵盖了博主在平常写C语言题目时经常犯的一些错误&#xff0c;在这里帮大家整理出来&#xff0c;一些易错点会帮大家标识出来&#xff0c;希望大家看完这篇文章后有所得&#xff0c;引以为戒~ 一、 题目&#xff1a; 解答&#xff1a; 首先在这个程序中…...

60.每日一练:回文数(力扣)

目录 问题描述 代码解决以及思想 解法&#xff08;一&#xff09; 知识点 解法&#xff08;二&#xff09; 问题描述 代码解决以及思想 解法&#xff08;一&#xff09; class Solution { public:bool isPalindrome(int x) {string arr to_string(x); // 将整数转换为…...

算法通关村第5关【青铜】| Hash和队列的特征

1.Hash基础 &#xff08;1&#xff09;基础 哈希也称为散列&#xff0c;通过算法变成固定长度的输出值&#xff0c;存入对应的位置 例如这个算法为取模算法&#xff0c;indexnumber 模 7 存入1到15 &#xff08;2&#xff09;碰撞处理 当多个元素映射到同一位置上时就产生…...

C++:函数

函数参数的传递机制 C的每个程序至少有一个函数&#xff0c;即主函数main()&#xff0c;函数也是类的方法的实现手段。C的函数包括两类&#xff1a;预定于函数和用户自定义函数。 函数的定义格式为&#xff1a; <返回值类型><函数名>(<参数列表>) <函…...

Linux网络编程:libevent事件通知库

文章目录&#xff1a; 一&#xff1a;libevent库 二&#xff1a;libevent框架 1.常规事件event 1.1 创建事件event&#xff08;event_new&#xff09; 1.2 添加事件到 event_base&#xff08;event_add&#xff09; 1.3 从event_base上摘下事件&#xff08;event_del&a…...

java.lang.reflect.InvocationTargetException:null报未知异常

在项目上线过程中&#xff0c;突然出现大量异常信息&#xff0c;堆栈信息如下&#xff1a; java.lang.reflect.InvocationTargetException: null at jdk .internal.reflect.GeneratedMethodAccessor792 .invoke(Unknown Source) ~[?:?] at jdk.internal.reflect.DelegatingM…...

MySQL高级篇——MySQL架构篇1(Linux下MySQL8的安装与使用)

目录 0 安装前0.1 Linux系统及工具的准备0.2 查看是否安装过MySQL0.3 MySQL的卸载 1 MySQL8的Linux版安装1.1 MySQL的4大版本1.2 下载MySQL指定版本1.3 CentOS7下检查MySQL依赖1.4 CentOS7下MySQL安装过程 2 MySQL登录2.1 首次登录2.2 修改密码2.3 设置远程登录 3 MySQL 8 的密…...

解决 go mod tidy 加载模块超时

如果go mod tidy 加载模块超时 解决方法 修改GOPROXY: 查看go环境相关信息&#xff1a; go envgo env -w GOPROXYhttps://goproxy.cn...

金融市场中的机器学习;快手推出自研语言模型“快意”

&#x1f989; AI新闻 &#x1f680; OpenAI可能面临《纽约时报》的起诉&#xff0c;侵犯知识产权引发争议 摘要&#xff1a;OpenAI使用《纽约时报》的文章和图片来训练AI模型&#xff0c;违反了《纽约时报》的服务条款&#xff0c;可能面临巨大损失。此前&#xff0c;也有其…...