Spring Boot 配置主从数据库实现读写分离

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring Boot 配置主从数据库实现读写分离

 一、前言

现在的 Web 应用大都是读多写少。除了缓存以外还可以通过数据库 “主从复制” 架构,把读请求路由到从数据库节点上,实现读写分离,从而大大提高应用的吞吐量。

通常,我们在 Spring Boot 中只会用到一个数据源,即通过 spring.datasource 进行配置。前文 《在 Spring Boot 中配置和使用多个数据源》 介绍了一种在 Spring Boot 中定义、使用多个数据源的方式。但是这种方式对于实现 “读写分离” 的场景不太适合。首先,多个数据源都是通过 @Bean 定义的,当需要新增额外的从数据库时需要改动代码,非常不够灵活。其次,在业务层中,如果需要根据读、写场景切换不同数据源的话只能手动进行。

对于 Spring Boot “读写分离” 架构下的的多数据源,我们需要实现如下需求:

    1. 可以通过配置文件新增数据库(从库),而不不需要修改代码。
    2. 自动根据场景切换读、写数据源,对业务层是透明的。

    幸运的是,Spring Jdbc 模块类提供了一个 AbstractRoutingDataSource 抽象类可以实现我们的需求。

    它本身也实现了 DataSource 接口,表示一个 “可路由” 的数据源。

    核心的代码如下:

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
        // 维护的所有数据源
        @Nullable
        private Map<Object, DataSource> resolvedDataSources;
        // 默认的数据源
        @Nullable
        private DataSource resolvedDefaultDataSource;
        // 获取 Jdbc 连接
        @Override
        public Connection getConnection() throws SQLException {
            return determineTargetDataSource().getConnection();
        }
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return determineTargetDataSource().getConnection(username, password);
        }
        // 获取目标数据源
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            // 调用  determineCurrentLookupKey() 抽象方法,获取 resolvedDataSources 中定义的 key。
            Object lookupKey = determineCurrentLookupKey();
            DataSource dataSource = this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }
            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            }
            return dataSource;
        }
        // 抽象方法,返回 resolvedDataSources 中定义的 key。需要自己实现
        @Nullable
        protected abstract Object determineCurrentLookupKey();
    }

    image.gif

    核心代码如上,它的工作原理一目了然。它在内部维护了一个 Map<Object, DataSource> 属性,维护了多个数据源。

    当尝试从 AbstractRoutingDataSource 数据源获取数据源连接对象 Connection 时,会调用 determineCurrentLookupKey() 方法得到一个 Key,然后从数据源 Map<Object, DataSource> 中获取到真正的目标数据源,如果 Key 或者是目标数据源为 null 则使用默认的数据源。

    得到目标数据数据源后,返回真正的 Jdbc 连接。这一切对于使用到 Jdbc 的组件(Repository、JdbcTemplate 等)来说都是透明的。

    了解了 AbstractRoutingDataSource 后,我们来看看如何使用它来实现 “读写分离”。

    二、实现思路

    首先,创建自己的 AbstractRoutingDataSource 实现类。把它的默认数据源 resolvedDefaultDataSource 设置为主库,从库则保存到 Map<Object, DataSource> resolvedDataSources 中。

    在 Spring Boot 应用中通常使用 @Transactional 注解来开启声明式事务,它的默认传播级别为 REQUIRED,也就是保证多个事务方法之间的相互调用都是在同一个事务中,使用的是同一个 Jdbc 连接。它还有一个 readOnly 属性表示是否是只读事务。

    于是,我们可以通过 AOP 技术,在事务方法执行之前,先获取到方法上的 @Transactional 注解从而判断是读、还是写业务。并且把 “读写状态” 存储到线程上下文(ThreadLocal)中!

    在 AbstractRoutingDataSource 的 determineCurrentLookupKey 方法中,我们就可以根据当前线程上下文中的 “读写状态” 判断当前是否是只读业务,如果是,则返回从库 resolvedDataSources 中的 Key,反之则返回 null 表示使用默认数据源也就是主库。

    三、初始化数据库

    首先,在本地创建 4 个不同名称的数据库,用于模拟 “MYSQL 主从” 架构。

    -- 主库
    CREATE DATABASE `demo_master` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
    -- 从库
    CREATE DATABASE `demo_slave1` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
    -- 从库
    CREATE DATABASE `demo_slave2` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
    -- 从库
    CREATE DATABASE `demo_slave3` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

    image.gif

    如上,创建了 4 个数据库。1 个主库,3 个从库。它们本质上毫无关系,并不是真正意义上的主从架构,这里只是为了方便演示。

    接着,在这 4 个数据库下依次执行如下 SQL 创建一张名为 test 的表。

    该表只有 2 个字段,1 个是 id 表示主键,一个是 name 表示名称。

    CREATE TABLE `test` (
      `id` int NOT NULL COMMENT 'ID',
      `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

    image.gif

    最后,初始化数据。往不同的数据库插入对应的记录。

    INSERT INTO `demo_master`.`test` (`id`, `name`) VALUES (1, 'master');
    INSERT INTO `demo_slave1`.`test` (`id`, `name`) VALUES (1, 'slave1');
    INSERT INTO `demo_slave2`.`test` (`id`, `name`) VALUES (1, 'slave2');
    INSERT INTO `demo_slave3`.`test` (`id`, `name`) VALUES (1, 'slave3');

    image.gif

    不同数据库节点下 test 表中的 name 字段不同,用于区别不同的数据库节点。

    四、创建应用

    创建 Spring Boot 应用,添加 spring-boot-starter-jdbc 和 mysql-connector-j (MYSQL 驱动)依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>

    image.gif

    五、配置定义

    我们需要在 application.yaml 中定义上面创建好的所有主、从数据库。

    app:
      datasource:
        master: # 唯一主库
          jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
          username: root
          password: root
        slave: # 多个从库
          slave1:
            jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
            username: root
            password: root
          slave2:
            jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
            username: root
            password: root
          slave3:
            jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave3?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
            username: root
            password: root

    image.gif

    在 app.datasource.master 下配置了唯一的一个主库,也就是写库。然后在 app.datasource.slave 下以 Map 形式配置了多个从库(也就是读库),每个从库使用自定义的名称作为 Key。

    数据源的实现使用的是默认的 HikariDataSource,并且数据源的配置是按照 HikariConfig 类定义的。也就是说,你可以根据 HikariConfig 的属性在配置中添加额外的设置。

    有了配置后,还需要定义对应的配置类,如下:

    package cn.springdoc.demo.db;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Properties;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.context.properties.bind.ConstructorBinding;
    @ConfigurationProperties(prefix = "app.datasource")  //  配置前缀
    public class MasterSlaveDataSourceProperties {
        // 主库
        private final Properties master;
        // 从库
        private final Map<String, Properties> slave;
        @ConstructorBinding // 通过构造函数注入配置文件中的值
        public MasterSlaveDataSourceProperties(Properties master, Map<String, Properties> slave) {
            super();
            Objects.requireNonNull(master);
            Objects.requireNonNull(slave);
            this.master = master;
            this.slave = slave;
        }
        public Properties master() {
            return master;
        }
        public Map<String, Properties> slave() {
            return slave;
        }
    }

    image.gif

    还需要在 main 类上使用 @EnableConfigurationProperties 注解来加载我们的配置类:

    package cn.springdoc.demo;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import cn.springdoc.demo.db.MasterSlaveDataSourceProperties;
    @SpringBootApplication
    @EnableAspectJAutoProxy
    @EnableConfigurationProperties(value = {MasterSlaveDataSourceProperties.class}) // 指定要加载的配置类
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }

    image.gif

    这里还使用 @EnableAspectJAutoProxy 开启了 AOP 的支持,后面会用到。

    六、创建 MasterSlaveDataSourceMarker

    创建一个 MasterSlaveDataSourceMarker 类,用于维护当前业务的 “读写状态”。

    package cn.springdoc.demo.db;
    public class MasterSlaveDataSourceMarker {
        private static final ThreadLocal<Boolean> flag = new ThreadLocal<Boolean>();
        // 返回标记
        public static Boolean get() {
            return flag.get();
        }
        // 写状态,标记为主库
        public static void master() {
            flag.set(Boolean.TRUE);
        }
        // 读状态,标记为从库
        public static void slave() {
            flag.set(Boolean.FALSE);
        }
        // 清空标记
        public static void clean() {
            flag.remove();
        }
    }

    image.gif

    通过 ThreadLocal<Boolean> 在当前线程中保存当前业务的读写状态。

    如果 get() 返回 null 或者 true 则表示非只读,需要使用主库。反之则表示只读业务,使用从库。

    七、创建 MasterSlaveDataSourceAop

    创建 MasterSlaveDataSourceAop 切面类,在事务方法开始之前执行。

    package cn.springdoc.demo.db;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)  // 在事务开始之前执行
    public class MasterSlaveDataSourceAop {
        static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSourceAop.class);
        @Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
        public void txMethod () {}
        @Around("txMethod()")
        public Object handle (ProceedingJoinPoint joinPoint) throws Throwable {
            // 获取当前请求的主从标识
            try {
                // 获取事务方法上的注解
                Transactional transactional = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Transactional.class);
                if (transactional != null && transactional.readOnly()) {
                    log.info("标记为从库");
                    MasterSlaveDataSourceMarker.slave();    // 只读,从库
                } else {
                    log.info("标记为主库");
                    MasterSlaveDataSourceMarker.master(); // 可写,主库
                }
                // 执行业务方法
                Object ret = joinPoint.proceed();
                return ret;
            } catch (Throwable e) {
                throw e;
            } finally {
                MasterSlaveDataSourceMarker.clean();
            }
        }
    }

    image.gif

    首先,通过 @Order(Ordered.HIGHEST_PRECEDENCE) 注解保证它必须比声明式事务 AOP 更先执行。

    该 AOP 会拦截所有声明了 @Transactional 的方法,在执行前从该注解获取 readOnly 属性从而判断是否是只读业务,并且在 MasterSlaveDataSourceMarker 标记。

    八、创建 MasterSlaveDataSource

    现在,创建 AbstractRoutingDataSource 的实现类 MasterSlaveDataSource:

    package cn.springdoc.demo.db;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    public class MasterSlaveDataSource extends AbstractRoutingDataSource {
        static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSource.class);
        // 从库的 Key 列表
        private List<Object> slaveKeys;
        // 从库 key 列表的索引
        private AtomicInteger index = new AtomicInteger(0);
        @Override
        protected Object determineCurrentLookupKey() {
            // 当前线程的主从标识
            Boolean master = MasterSlaveDataSourceMarker.get();
            if (master == null || master || this.slaveKeys.isEmpty()) {
                // 主库,返回 null,使用默认数据源
                log.info("数据库路由:主库");
                return null;
            }
            // 从库,从 slaveKeys 中选择一个 Key
            int index = this.index.getAndIncrement() % this.slaveKeys.size();
            if (this.index.get() > 9999999) {
                this.index.set(0); 
            }
            Object key = slaveKeys.get(index);
            log.info("数据库路由:从库 = {}", key);
            return key;
        }
        public List<Object> getSlaveKeys() {
            return slaveKeys;
        }
        public void setSlaveKeys(List<Object> slaveKeys) {
            this.slaveKeys = slaveKeys;
        }
    }

    image.gif

    其中,定义了一个 List<Object> slaveKeys 字段,用于存储在配置文件中定义的所有从库的 Key。

    在 determineCurrentLookupKey 方法中,判断当前业务的 “读写状态”,如果是只读则通过 AtomicInteger 原子类自增后从 slaveKeys 轮询出一个从库的 Key。反之则返回 null 使用主库。

    九、创建 MasterSlaveDataSourceConfiguration 配置类

    最后,需要在 @Configuration 配置类中,创建 MasterSlaveDataSource 数据源 Bean。

    package cn.springdoc.demo.db;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    import javax.sql.DataSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    @Configuration
    public class MasterSlaveDataSourceConfiguration {
        @Bean
        public DataSource dataSource(MasterSlaveDataSourceProperties properties) {
            MasterSlaveDataSource dataSource = new MasterSlaveDataSource();
            // 主数据库
            dataSource.setDefaultTargetDataSource(new HikariDataSource(new HikariConfig(properties.master())));
            // 从数据库
            Map<Object, Object> slaveDataSource = new HashMap<>();
            // 从数据库 Key
            dataSource.setSlaveKeys(new ArrayList<>());
            for (Map.Entry<String,Properties> entry : properties.slave().entrySet()) {
                if (slaveDataSource.containsKey(entry.getKey())) {
                    throw new IllegalArgumentException("存在同名的从数据库定义:" + entry.getKey());
                }
                slaveDataSource.put(entry.getKey(), new HikariDataSource(new HikariConfig(entry.getValue())));
                dataSource.getSlaveKeys().add(entry.getKey());
            }
            // 设置从库
            dataSource.setTargetDataSources(slaveDataSource);
            return dataSource;
        }
    }

    image.gif

    首先,通过配置方法注入配置类,该类定义了配置文件中的主库、从库属性。

    使用 HikariDataSource 实例化唯一主库数据源、和多个从库数据源,并且设置到 MasterSlaveDataSource 对应的属性中。

    同时还存储每个从库的 Key,且该 Key 不允许重复。

    十、测试

    1、创建 TestService

    创建用于测试的业务类。

    package cn.springdoc.demo.service;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    @Service
    public class TestService {
        final JdbcTemplate jdbcTemplate;
        public TestService(JdbcTemplate jdbcTemplate) {
            super();
            this.jdbcTemplate = jdbcTemplate;
        }
        // 只读
        @Transactional(readOnly = true)
        public String read () {
            return this.jdbcTemplate.queryForObject("SELECT `name` FROM `test` WHERE id = 1;", String.class);
        } 
        // 先读,再写
        @Transactional
        public String write () {
            this.jdbcTemplate.update("UPDATE `test` SET `name` = ? WHERE id = 1;", "new name");
            return this.read();
        }
    }

    image.gif

    通过构造函数注入 JdbcTemplate(spring jdbc 模块自动配置的)。

    Service 类中定义了 2 个方法。

      • read():只读业务,从表中检索 name 字段返回。
      • write:可写业务,先修改表中的 name 字段值为: new name,然后再调用 read() 方法读取修改后的结果、返回。

      2、创建测试类

      创建测试类,如下:

      package cn.springdoc.demo.test;
      import org.junit.jupiter.api.Test;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
      import cn.springdoc.demo.service.TestService;
      @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
      public class DemoApplicationTests {
          static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);
          @Autowired
          TestService testService;
          @Test
          public void test() throws Exception {
              // 连续4次读
              log.info("read={}", this.testService.read());
              log.info("read={}", this.testService.read());
              log.info("read={}", this.testService.read());
              log.info("read={}", this.testService.read());
              // 写
              log.info("write={}", this.testService.write());
          }
      }

      image.gif

      在测试类方法中,连续调用 4 次 TestService 的 read() 方法。由于这是一个只读方法,按照我们的设定,它会在 3 个从库之间轮询使用。由于我们故意把三个从库 test 表中 name 的字段值设置得不一样,所以这里可以通过返回的结果看出来是否符合我们的预期。

      最后调用了一次 write() 方法,按照设定会路由到主库。先 UPDATE 修改数据,再调用 read() 读取数据,虽然 read() 设置了 @Transactional(readOnly = true),但因为入口方法是 write(),所以 read() 还是会从主库读取数据(默认的事务传播级别)。

      执行测试,输出的日志如下:

      [           main] c.s.demo.db.MasterSlaveDataSourceAop     : 标记为从库
      [           main] c.s.demo.db.MasterSlaveDataSource        : 数据库路由:从库 = slave1
      [           main] c.s.demo.test.DemoApplicationTests       : read=slave1
      [           main] c.s.demo.db.MasterSlaveDataSourceAop     : 标记为从库
      [           main] c.s.demo.db.MasterSlaveDataSource        : 数据库路由:从库 = slave2
      [           main] c.s.demo.test.DemoApplicationTests       : read=slave2
      [           main] c.s.demo.db.MasterSlaveDataSourceAop     : 标记为从库
      [           main] c.s.demo.db.MasterSlaveDataSource        : 数据库路由:从库 = slave3
      [           main] c.s.demo.test.DemoApplicationTests       : read=slave3
      [           main] c.s.demo.db.MasterSlaveDataSourceAop     : 标记为从库
      [           main] c.s.demo.db.MasterSlaveDataSource        : 数据库路由:从库 = slave1
      [           main] c.s.demo.test.DemoApplicationTests       : read=slave1
      [           main] c.s.demo.db.MasterSlaveDataSourceAop     : 标记为主库
      [           main] c.s.demo.db.MasterSlaveDataSource        : 数据库路由:主库
      [           main] c.s.demo.test.DemoApplicationTests       : write=new name

      image.gif

      你可以看到,对于只读业务。确实轮询了三个不同的从库,符合预期。最后的 write() 方法也成功地路由到了主库,执行了修改并且返回了修改后的结果。

      十一总结

      通过 AbstractRoutingDataSource 可以不使用任何第三方中间件就可以在 Spring Boot 中实现数据源 “读写分离”,这种方式需要在每个业务方法上通过 @Transactional 注解明确定义是读还是写。




      Doker 做好产品,做暖心服务的数码品牌!!!

      文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群!!!你的支持和鼓励是我创作的动力❗❗❗

      官网:Doker 多克; 官方旗舰店首页-Doker 多克创新官方店-淘宝网全品折扣大优惠!!

      相关实践学习
      如何快速连接云数据库RDS MySQL
      本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
      全面了解阿里云能为你做什么
      阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
      DOKER
      +关注
      目录
      打赏
      0
      0
      0
      0
      791
      分享
      相关文章
      【YashanDB知识库】原生mysql驱动配置连接崖山数据库
      【YashanDB知识库】原生mysql驱动配置连接崖山数据库
      【YashanDB知识库】原生mysql驱动配置连接崖山数据库
      |
      6天前
      |
      微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
      本文介绍了在Spring Boot中配置Swagger2的方法。通过创建一个配置类,添加`@Configuration`和`@EnableSwagger2`注解,使用Docket对象定义API文档的详细信息,包括标题、描述、版本和包路径等。配置完成后,访问`localhost:8080/swagger-ui.html`即可查看接口文档。文中还提示了可能因浏览器缓存导致的问题及解决方法。
      39 0
      微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
      【YashanDB知识库】数据库一主一备部署及一主两备部署时,主备手动切换方法及自动切换配置
      【YashanDB知识库】数据库一主一备部署及一主两备部署时,主备手动切换方法及自动切换配置
      【YashanDB知识库】数据库一主一备部署及一主两备部署时,主备手动切换方法及自动切换配置
      微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
      本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
      29 0
      微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
      本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
      14 0
      微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
      在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
      27 0
      Navicat Premium 17 最新版下载与配置:5分钟完成企业级数据库工具部署
      Navicat Premium 17 是一款支持多种主流数据库(如 MySQL、Oracle、PostgreSQL 等)的多数据库管理工具,提供可视化数据建模、SQL 编辑和数据同步等功能。试用版提供 14 天全功能体验,商业版支持跨平台使用。安装环境要求 Windows 10/11 或 macOS 12.0+,最低配置为 4GB 内存。下载并解压安装包后,按步骤启动安装程序、接受许可协议、自定义安装路径并完成安装。首次运行时需激活许可证并配置数据库连接。常见问题包括无法写入注册表、试用期续费及连接数据库权限问题。高级功能涵盖 SSH 通道加速、自动化任务调度和性能调优建议。
      122 19
      微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
      本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
      36 0
      微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
      在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
      26 0

      热门文章

      最新文章