Skip to content

Latest commit

 

History

History
419 lines (337 loc) · 15.7 KB

File metadata and controls

419 lines (337 loc) · 15.7 KB

7.1 事务管理

7.1.1 概述

事务是一组相关操作的逻辑单元,它确保这组操作要么全部成功,要么全部失败,从而保证数据的一致性和完整性。事务通常用于数据库操作,以确保数据的完整性和一致性。

  1. 原子性(Atomicity)
  • 原子性是指事务中的所有操作要么全部成功,要么全部失败。事务中的操作不能部分完成。
  1. 一致性(Consistency)
  • 一致性是指事务必须将数据库从一个一致状态转换到另一个一致状态。在事务开始之前和结束之后,数据库的状态都必须是一致的。
  1. 隔离性(Isolation)
  • 隔离性是指并发执行的事务之间不会互相干扰。每个事务都在独立的环境中执行,不会影响其他事务的执行。
  1. 持久性(Durability)
  • 持久性是指一旦事务成功提交,其结果就会永久保存在数据库中。即使在系统故障后,事务的结果也不会丢失。

7.1.2 FIT 事务的使用方式

编程式事务

FIT 提供事务管理程序,为调用方提供获取事务的入口。调用方可根据事务的元数据获取一个事务实例,其中:

  • TransactionManager,管理整个事务体系,主要提供获取事务的能力。

  • TransactionMetadata,事务的元数据定义信息,如事务的隔离级别、传播策略、超时时间等。

  • Transaction,定义事务的属性和行为,例如事务是否处于活动状态,提交、回滚事务等。

    首先,需要构造事务元数据TransactionMetadata,示例如下:

final TransactionPropagationPolicy propagation = TransactionPropagationPolicy.NESTED;
final TransactionIsolationLevel isolation = TransactionIsolationLevel.SERIALIZABLE;
final int timeout = 100;
final boolean readonly = true;

TransactionMetadata metadata = TransactionMetadata.custom()
    .name(name)
    .propagation(propagation)
    .isolation(isolation)
    .timeout(timeout)
    .readonly(readonly)
    .build();

其中,TransactionMetadata是一个接口,custom()函数调用了DefaultTransactionMetadataBuilder(),使用建造者模式进行了实例化,用户可以自行制定参数,也可以使用withDefault()方法,为指定的事务元数据包装默认值。 TransactionMetadata的方法如下:

方法名 参数 返回值 方法说明
name() \ String 获取事务的名称
propagation() \ TransactionPropagationPolicy 获取事务的传播策略
isolation() \ TransactionIsolationLevel 获取事务的隔离级别
timeout() \ int 获取事务的超时时间
readonly() \ boolean 获取一个值,该值指示事务是否是只读的
empty() \ TransactionMetadata 获取空的事务元数据实例
custom() \ TransactionMetadataBuilder 返回一个构建程序,用以定制化事务元数据信息
withDefault() TransactionMetadata TransactionMetadata 为指定的事务元数据包装默认值

然后,用事务的元数据构造transactionManager,再用TransactionMetadata作为参数构造对应事务Transaction,就可以通过Transaction实现对事务的操作,示例如下:

TransactionManager manager = TransactionManager.create(beanContainer);
Transaction transaction = transactionManager.begin(metadata);
transaction.commit();
transaction.rollback();

其中,beanContainer属于当前插件的独立IoC容器,可以通过依赖注入的方式获取,具体请参考插件、IoC 容器和 Bean,示例如下:

public class BeanTest{
    private final BeanContainer beanContainer;
    
    public BeanTest(@Fit BeanContainer beanContainer) {
        this.beanContainer = beanContainer;
    }
}

FIT 提供了接口TransactionManager,方法如下:

方法名 参数 返回值 方法说明
dataSource() \ DataSource 获取事务管理程序所使用的数据源
begin() TransactionMetadata Transaction 开始事务
active() \ Transaction 获取当前事务
activate() Transaction void 激活指定事务
deactivate() Transaction void 失活指定事务
create() BeanContainer TransactionManager 创建事务管理器

Transaction是一个接口,主要功能是为事务提供定义,通常可以通过TransactionManagerbegin()方法建造一个实例,接口的方法如下:

方法名 参数 返回值 方法说明
metadata() \ TransactionMetadata 获取事务的元数据
manager() \ TransactionManager 获取所属的事务管理程序
parent() \ Transaction 获取父事务
connection() \ Connection 获取事务所使用的数据库连接
active() \ boolean 获取一个值,该值指示事务是否处于活动状态
complete() \ boolean 获取一个值,该值指示事务是否已完成
hasBackend() \ boolean 获取一个值,该值指示是否存在后端事务
commit() \ void 提交事务
rollback() \ void 回滚事务

基于注解的声明式事务

在类或方法上添加@Transactional注解来设置事务,示例代码如下:

   @Transactional
   public void create(Long id, String name) {
       this.repo.insert(new User(id, name));
   }

可通过添加@Transactional注解的参数来指定事务属性。

参数名 参数类型 参数值说明 默认值
name String 指示事务的名称 EMPTY
propagation TransactionPropagationPolicy 指示事务的传播策略 TransactionPropagationPolicy.REQUIRED
isolation TransactionIsolationLevel 指示事务的隔离级别 TransactionIsolationLevel.READ_COMMITTED
timeout int 指示事务的以秒为单位的超时时间 0
readonly boolean 指示事务是否是只读的 false

示例如下:

public static class TestService {
    @Transactional(propagation = TransactionPropagationPolicy.REQUIRES_NEW,
            isolation = TransactionIsolationLevel.READ_COMMITTED)
    public void run(Runnable action) {
        action.run();
    }
}

7.1.3 事务传播策略

事务传播策略是指在事务管理中,当一个方法被另一个方法调用时,如何处理事务的行为,在FIT中,TransactionPropagationPolicy为事务提供传播策略的定义,可在构造Metadata时指定参数对事务的传播策略进行设定,也可以使用注解@Transactional的参数进行设定。

参数名 参数值说明
REQUIRED 当不存在事务时,开始新的事务,否则加入当前事务。
SUPPORTS 当存在事务时,在当前事务中执行,否则不使用事务直接执行。
MANDATORY 必须在一个已有事务中运行,否则将抛出异常。
REQUIRES_NEW 不论当前是否存在事务,都将启动新的事务来执行。
NOT_SUPPORTED 不论当前是否存在事务,都不在事务中执行。
NEVER 不支持在事务中执行,如果存在事务,则抛出异常。
NESTED 如果当前存在事务,则开始嵌套事务并执行,否则开始新事务执行。

7.1.4 事务的隔离级别

事务的隔离级别是指在并发执行多个事务时,为了保证数据的一致性和完整性,事务之间的相互影响程度,不同的隔离级别会导致不同的锁机制和并发控制策略,在FIT中,TransactionIsolationLevel 为事务提供隔离级别的定义,可在构造Metadata时指定参数对事务的隔离级别进行设定,也可以使用注解@Transactional的参数进行设定。

参数名 参数值说明
NONE 不支持事务
READ_UNCOMMITTED 脏读、幻读、不可重复读都是支持的
READ_COMMITTED 不支持脏读,但支持不可重复读和幻读
REPEATABLE_READ 不支持脏读和不可重复读,允许幻读
SERIALIZABLE 脏读、不可重复读、幻读都不支持

7.2 Druid

Druid是一个高性能的数据库连接池,它提供了许多优秀的功能,如监控、统计、慢日志记录等,FIT 框架接入了Druid以供开发者配置数据源。

配置文件

首先,在项目pom文件中引入依赖以使用Druid模块:

<dependency>
    <groupId>org.fitframework.integration</groupId>
    <artifactId>fit-druid</artifactId>
    <version>${fit.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.12</version>
</dependency>

application.yml中配置数据源信息,示例如下:

fit:
  datasource:
    primary: 'sample-datasource' # 表示所选用的示例数据源。
    instances:
      sample-datasource:
        mode: 'shared' # 表示该数据源的模式,可选共享(shared)或独占(exclusive)模式。
        url: 'jdbc:postgresql://${ip}:${port}/' # 将 ip 换成数据库服务器的 ip 地址,将 port 换成数据库服务器监听的端口。
        username: '${username}' # 将 username 替换为数据库的名称。
        password: '${password}' # 将 password 替换为数据库的密码。
        druid:
          initialSize: ${initialSize} # 将 initialSize 替换为连接池的初始化连接数。
          minIdle: ${midIdle} # 将 minIdle 替换为连接池的最小空闲连接数。
          maxActive: ${maxActive} # 将 maxActive 替换为数据库连接池的最大活动连接数。
          ... # 可根据具体需求,添加连接池所需配置项。

配置完成后,FIT 框架自动按照配置内容连接 Fruid,开发者无需进行额外操作。

7.3 MyBatis

MyBatis 是一个优秀的持久层框架,用于将 Java 对象和关系数据库之间的操作解耦,使开发者能够专注于业务逻辑而不是数据库操作,FIT 框架接入了 MyBatis 以供开发者进行数据库处理。

配置文件

首先,在pom文件中添加MyBatis依赖:

<dependency>
    <groupId>org.fitframework.integration</groupId>
    <artifactId>fit-mybatis</artifactId>
    <version>${fit.version}</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>${mybatis.version}</version>
    <scope>provided</scope>
</dependency>

FIT 已内置MyBatis的配置文件初始化,无需另外创建 mybatis-config.xml 文件,仅需在application.yml文件中设置MyBatisSQL映射文件路径:

mybatis:
  mapper-locations: '**/*.xml'

配置完成后,开发者可以正常开发MyBatisMapper文件进行数据库操作。

FIT 支持 MyBatis 的下划线自动转驼峰格式配置, 默认为关闭状态。在application.yml文件中追加如下配置,即可开启。

mybatis:
  map-underscore-to-camelcase: true

使用示例

以下提供一个搭配使用DruidMyBatis的示例。

首先,配置好依赖及MyBatis文件路径,然后定义数据的实体类UserDo和传输实体类UserData

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 表示用户资源的实体类。
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDo {
    private int id;
    private String name;
    private String email;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 表示用户资源的传输实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserData {
    private int id;
    private String name;
    private String email;
}

定义处理用户资源的服务接口类:

/**
 * 用户资源的服务接口类。
 */
public interface UserService {
    /**
     * 根据用户唯一标识获取用户资源。
     */
    UserData getUserById(int id);

    /**
     * 根据用户资源插入用户信息。
     */
    void insertUser(UserData user);
}

定义处理用户资源的服务接口类的默认实现,其中通过依赖注入的方式获取UserMapper

/**
 * 用户资源的 Http 请求的服务层实现。
 */
@Component
public class DefaultUserService implements UserService {
    private final UserMapper userMapper;

    public DefaultUserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public UserData getUserById(int id) {
        UserDo userDo = this.userMapper.getUserById(id);
        UserData user = new UserData();
        user.setId(userDo.getId());
        user.setName(userDo.getName());
        user.setEmail(userDo.getEmail());
        return user;
    }

    @Override
    public void insertUser(UserData user) {
        UserDo userDo = new UserDo();
        userDo.setName(user.getName());
        userDo.setEmail(user.getEmail());
        this.userMapper.insertUser(userDo);
    }
}

定义用户资源接收HTTP方法的控制器:

// 表示用户资源的控制器。
@Component
@RequestMapping("/data")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 通过用户Id获取信息
     */
    @GetMapping(path = "/getUserById")
    public UserData getTask(@RequestParam("id") Integer userId) {
        return this.userService.getUserById(userId);
    }

    /**
     * 添加用户
     */
    @GetMapping(path = "/user")
    public void getTask(@RequestParam("name") String name, @RequestParam("email") String email) {
        UserData user = new UserData();
        user.setName(name);
        user.setEmail(email);
        this.userService.insertUser(user);
    }
}

自行创建一个数据表users,含idnameemail三个字段,其中id为自增主键,然后定义MyBatisMapper接口及对应的Mapper.xml文件:

/**
 * 表示用于 MyBatis 持久层引用的 User 接口。
 */
public interface UserMapper {
    /**
     * 根据用户唯一标识获取用户资源。
     */
    UserDo getUserById(int id);

    /**
     * 根据用户资源插入用户信息。
     */
    void insertUser(UserDo user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="modelengine.fit.jade.mapper.UserMapper">
    <select id="getUserById" resultType="modelengine.fit.jade.dto.UserDo">
        SELECT *
        FROM users
        WHERE id = #{id}
    </select>

    <insert id="insertUser" parameterType="modelengine.fit.jade.dto.UserDo">
        INSERT INTO users (name, email)
        VALUES (#{name}, #{email})
    </insert>
</mapper>

现在,您可以通过HTTP请求来对数据库进行对User的数据插入及id查询操作,示例:

http://localhost:8080/user?name=zhangsan&email=testEmail

7.4 风险告知说明

FIT 提供了数据源机制可以提供数据源的共享和独占模式,并实现了对Mybatis的支持。开发者请阅读相关文档,了解其使用逻辑,在这个过程中可能存在以下的风险点,请注意管控。

  1. 建议对隐私信息进行加密处理,并开启相应的解密配置,避免隐私泄露;
  2. 建议控制 mybatis 使用的xml文件的大小,过大的xml文件可能导致加载缓慢;
  3. 建议关注数据源的配置,避免不合理的配置导致数据库崩溃;