提到业务,咱们第一影响应该是数据库办理体系的一个重要概念。

业务(Transaction)是数据库办理体系(DBMS)中的一个概念,用于办理对数据库的一组操作,这些操作要么悉数成功履行,要么悉数回滚(撤销)。

业务一般由一系列数据库操作组成,例如刺进、更新、删去等。这些操作被视为一个逻辑上的单元,要么悉数履行成功,要么悉数不履行。业务具有以下四个特性,一般被称为 ACID 特性:

  • 原子性(Atomicity) :业务被视为一个不可分割的原子操作,要么悉数履行成功,要么悉数回滚。假如业务中的任何一个操作失利,整个业务将被回滚到初始状况,数据库不会受到部分操作的影响。
  • 共同性(Consistency) :业务在履行前和履行后,数据库的完整性束缚没有被损坏。这意味着业务有必要确保数据库从一个共同的状况转换到另一个共同的状况。
  • 阻隔性(Isolation) :并发履行的多个业务之间应该彼此阻隔,每个业务的操作应该与其他业务的操作彼此独立。阻隔性确保了每个业务在并发履行时不会彼此搅扰,避免了数据的不共同性。
  • 持久性(Durability) :一旦业务提交成功,其所做的修正将永久保存在数据库中,即便体系发生故障或重启,修正的数据也不会丢掉。

业务的运用能够确保数据库操作的共同性和可靠性,尤其在并发访问数据库的环境中,业务的阻隔功能够避免数据抵触和并发问题。

那么咱们在Spring Boot中假如完成对简略业务的处理呢?

那就随我一同来通过Spring Boot来简略完成一个业务吧:
引入相关依赖:

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

在Spring Boot的相关配置文件中配置数据库:

spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.datasource.primary.username=root
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create
@Entity
//@Data
//@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @Max(50)
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public User() {
    }
}

创立一个承载SQL的接口

public interface UserRepository extends JpaRepository<User, Long> {
    User findByName(String name);
    User findByNameAndAge(String name, Integer age);
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
}

最终创立一个测验类:

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Autowired
    private UserRepository userRepository;
    @Test
    //@Transactional //这个注解先不加履行,再一次履行的时分加然后看结果。
    public void test() throws Exception {
        // 创立10条记载
        userRepository.save(new User("AAA", 10));
        userRepository.save(new User("BBB", 20));
        userRepository.save(new User("CCC", 30));
        userRepository.save(new User("DDD", 40));
        userRepository.save(new User("EEE", 50));
        userRepository.save(new User("FFF", 60));
        userRepository.save(new User("GGG", 70));
        userRepository.save(new User("HHH", 80));
        userRepository.save(new User("III", 90));
        userRepository.save(new User("JJJ", 100));
        // 测验findAll, 查询一切记载
        Assert.assertEquals(10, userRepository.findAll().size());
        // 测验findByName, 查询姓名为FFF的User
        Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
        // 测验findUser, 查询姓名为FFF的User
        Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
        // 测验findByNameAndAge, 查询姓名为FFF而且年纪为60的User
        Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
        // 测验删去姓名为AAA的User
        userRepository.delete(userRepository.findByName("AAA"));
        // 测验findAll, 查询一切记载, 验证上面的删去是否成功
        Assert.assertEquals(9, userRepository.findAll().size());
    }
}

因为咱们在实体类中通过@Max注解为User的age设置了最大值为50,这样就能够通过创立User实体的age属性超越50的时分就能够触发反常,也便是我在上边测验类的履行年纪大于50岁的数据都会被终止:

HHH000346: Error during managed flush [Validation failed for classes [com.miaow.demo.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
	ConstraintViolationImpl{interpolatedMessage='最大不能超越50', propertyPath=age, rootBeanClass=class com.miaow.demo.User, messageTemplate='{javax.validation.constraints.Max.message}'}
]]

假如咱们去查数据库表会发现只存在包含50岁以下的一切数据,其他数据就增加失利了。

这个时分咱们就需要进行业务处理了,因为他刺进了50岁曾经的,但是50岁今后的都没了,这样就不满意业务的原子性了,要么都做要么都不做,咱们能够通过@Transactional来完成,之后咱们在瞅瞅数据库表中数据,咱们发现在没加@Transactional的时分,他会履行一部分,另一部分不履行,也便是不满意咱们业务中的原子性,在咱们加了@Transactional当咱们履行收到阻碍的时分,或者被反常终止的时分,会运用@Rollback注解让咱们的相关类都能够在结束的时分得到回滚,也便是,这件工作,要么做,要做就要做成功,要么不做。

业务的阻隔等级

提到了业务,那么咱们需要提及的是阻隔等级,业务的阻隔等级是咱们的数据库办理体系中用来操控并访问数据的一种机制,他界说了业务在一起访问数据库的时分,对其他业务的影响程度和能够见性的一种规矩。

咱们常见的阻隔等级:

  • 读未提交(Read Uncommitted) :最低的阻隔等级,答应一个业务读取另一个业务未提交的数据。可能会导致脏读(Dirty Read)问题。
  • 读已提交(Read Committed) :确保一个业务只能读取到现已提交的数据。避免了脏读问题,但可能会导致不可重复读(Non-repeatable Read)问题。
  • 可重复读(Repeatable Read)确保一个业务在履行期间屡次读取相同的数据时,能够得到共同的结果避免了不可重复读问题,但可能会导致幻读(Phantom Read)问题
  • 串行化(Serializable)最高的阻隔等级,通过强制业务串行履行来避免并发问题。能够避免脏读、不可重复读和幻读问题,但会下降并发功能
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
/**
 * Enumeration that represents transaction isolation levels for use
 * with the {@link Transactional} annotation, corresponding to the
 * {@link TransactionDefinition} interface.
 *
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @since 1.2
 */
public enum Isolation {
	/**
	 * Use the default isolation level of the underlying datastore.
	 * All other levels correspond to the JDBC isolation levels.
	 * @see java.sql.Connection
	 */
	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
	/**
	 * A constant indicating that dirty reads, non-repeatable reads and phantom reads
	 * can occur. This level allows a row changed by one transaction to be read by
	 * another transaction before any changes in that row have been committed
	 * (a "dirty read"). If any of the changes are rolled back, the second
	 * transaction will have retrieved an invalid row.
	 * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
	 */
	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
	/**
	 * A constant indicating that dirty reads are prevented; non-repeatable reads
	 * and phantom reads can occur. This level only prohibits a transaction
	 * from reading a row with uncommitted changes in it.
	 * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
	 */
	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
	/**
	 * A constant indicating that dirty reads and non-repeatable reads are
	 * prevented; phantom reads can occur. This level prohibits a transaction
	 * from reading a row with uncommitted changes in it, and it also prohibits
	 * the situation where one transaction reads a row, a second transaction
	 * alters the row, and the first transaction rereads the row, getting
	 * different values the second time (a "non-repeatable read").
	 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
	 */
	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
	/**
	 * A constant indicating that dirty reads, non-repeatable reads and phantom
	 * reads are prevented. This level includes the prohibitions in
	 * {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation
	 * where one transaction reads all rows that satisfy a {@code WHERE}
	 * condition, a second transaction inserts a row that satisfies that
	 * {@code WHERE} condition, and the first transaction rereads for the
	 * same condition, retrieving the additional "phantom" row in the second read.
	 * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
	 */
	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
	private final int value;
	Isolation(int value) {
		this.value = value;
	}
	public int value() {
		return this.value;
	}
}

针对这个枚举类咱们来看一下,咱们的Spring Boot的Transaction 中界说的5个表明阻隔等级值:

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}
  • DEFAULT:这是默认值,表明运用底层数据库的默认阻隔等级。对大部分数据库而言,一般这值便是:READ_COMMITTED。
  • READ_UNCOMMITTED:该阻隔等级表明一个业务能够读取另一个业务修正但还没有提交的数据。该等级不能避免脏读和不可重复读,因而很少运用该阻隔等级。
  • READ_COMMITTED:该阻隔等级表明一个业务只能读取另一个业务现已提交的数据。该等级能够避免脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ:该阻隔等级表明一个业务在整个过程中能够屡次重复履行某个查询,而且每次回来的记载都相同。即便在屡次查询之间有新增的数据满意该查询,这些新增的记载也会被忽略。该等级能够避免脏读和不可重复读。
  • SERIALIZABLE:一切的业务顺次逐一履行,这样业务之间就完全不可能发生搅扰,也便是说,该等级能够避免脏读、不可重复读以及幻读。但是这将严重影响程序的功能。一般情况下也不会用到该等级。

传达行为

所谓的业务传达行为值的是,假如在开始履行当时业务之前,一个业务上下文以及存在,此时若有若干选项能够指定一个业务性办法的履行行为。

package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
/**
 * Enumeration that represents transaction propagation behaviors for use
 * with the {@link Transactional} annotation, corresponding to the
 * {@link TransactionDefinition} interface.
 *
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @since 1.2
 */
public enum Propagation {
	/**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>This is the default setting of a transaction annotation.
	 */
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
	/**
	 * Support a current transaction, execute non-transactionally if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>Note: For transaction managers with transaction synchronization,
	 * PROPAGATION_SUPPORTS is slightly different from no transaction at all,
	 * as it defines a transaction scope that synchronization will apply for.
	 * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
	 * will be shared for the entire specified scope. Note that this depends on
	 * the actual synchronization configuration of the transaction manager.
	 * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
	 */
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
	/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
	/**
	 * Create a new transaction, and suspend the current transaction if one exists.
	 * Analogous to the EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code javax.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Java EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
	/**
	 * Execute non-transactionally, suspend the current transaction if one exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 * on all transaction managers. This in particular applies to
	 * {@link org.springframework.transaction.jta.JtaTransactionManager},
	 * which requires the {@code javax.transaction.TransactionManager} to be
	 * made available to it (which is server-specific in standard Java EE).
	 * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
	 */
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
	/**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */
	NEVER(TransactionDefinition.PROPAGATION_NEVER),
	/**
	 * Execute within a nested transaction if a current transaction exists,
	 * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
	 * <p>Note: Actual creation of a nested transaction will only work on specific
	 * transaction managers. Out of the box, this only applies to the JDBC
	 * DataSourceTransactionManager when working on a JDBC 3.0 driver.
	 * Some JTA providers might support nested transactions as well.
	 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
	 */
	NESTED(TransactionDefinition.PROPAGATION_NESTED);
	private final int value;
	Propagation(int value) {
		this.value = value;
	}
	public int value() {
		return this.value;
	}
}

业务传达行为是指在多个业务之间进行操作时,业务的行为办法。常见的业务传达行为包含:

  • REQUIRED:假如当时存在业务,则参加该业务,假如没有业务,则创立一个新的业务。这是默认的传达行为。
  • REQUIRES_NEW:无论当时是否存在业务,都创立一个新的业务。假如当时存在业务,则将其挂起。
  • SUPPORTS:假如当时存在业务,则参加该业务,假如没有业务,则以非业务办法履行。
  • NOT_SUPPORTED:以非业务办法履行操作,假如当时存在业务,则将其挂起。
  • MANDATORY:假如当时存在业务,则参加该业务,假如没有业务,则抛出反常。
  • NEVER:以非业务办法履行操作,假如当时存在业务,则抛出反常。
  • NESTED:假如当时存在业务,则在嵌套业务中履行。嵌套业务是外部业务的一部分,能够独立提交或回滚,但是假如外部业务回滚,则嵌套业务也会回滚。

业务传达行为能够根据具体的业务需求来选择,以确保业务的共同性和可靠性。不同的传达行为能够在多个业务之间提供灵敏的操控和办理。

指定办法:通过运用propagation属性设置,例如:

@Transactional(propagation = Propagation.REQUIRED)