互联网应用主流框架整合之Spring Boot开发

news2024/11/18 4:17:33

Spring Boot数据库开发

通常SpringBoot数据库开发,会引入spring-boot-starter-jdbc,而如果引入了spring-boot-starter-jdbc,但没有可用的数据源或者没有配置,那么在运行Spring Boot时会出现异常,因为spring-boot-starter-jdbc内部会自动配置一个数据源,如果不能连接就会引发异常

假如系统用的数据源来自MySQL,但还没有配置,可以在POM里引入一个内存数据库(也可以配置成持久化存储在硬盘上),轻量级的关系型数据库管理系统,如下所示

        <!-- 引入H2数据库的依赖 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

然后在启动SpringBoot,访问如下地址就能看到H2的访问页面,但实际上这个数据库并没有单独安装
在这里插入图片描述
在启动日志里也能看到这个数据库的启动
在这里插入图片描述

也可以取消SpringBoot默认数据源的创建,如下代码所示

package com.sbdev;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
//exclude = DataSourceAutoConfiguration.class 禁止自动配置数据源
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SbDevApplication {
    @GetMapping("/test")
    public Map<String, String> test() {
        Map<String, String> map = new HashMap<>();
        map.put("success", "true");
        map.put("message", "我的第一个Spring Boot程序");
        return map;
    }
    public static void main(String[] args) {
        SpringApplication.run(SbDevApplication.class, args);
    }

}

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)表示启动SpringBoot,不再使用DataSourceAutoConfiguration这个配置类,如果没有配置数据源,运行时就不再发生异常,这也变相的说明SpringBoot的配置理念,使用配置类来生成对应的Bean,而Bean的属性可以通过配置文件自定义

正常情况在开发应用的时候都会提前配置好,需要确认自己的环境,如下列情况不同的数据库版本配置对应的数据库驱动及参数

服务启动过程中经常会遇到的一个问题是无法创建连接,其中有一种原因是数据库的版本和连接驱动类的版本不匹配,如果使用的是MySQL8.0以下的版本,那在POM中添加的依赖应该是

	<!-- 引入MySQL数据库连接驱动依赖 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.29</version>
	</dependency>

配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.jdbc.Driver");而如果MySQL用的是8.0以上的版本,那么POM中应该添加的依赖是

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version> <!-- 使用实际的版本号 -->
    </dependency>

或者

    <!-- 引入MySQL连接器,用于在运行时连接MySQL数据库 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>

配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");

配置好这些之后,在SpringBoot的配置文件application.yml中,写入数据源配置,如下所示

spring.application.name:
  - SBDev

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: Ms123!@#


server:
    servlet:
      context-path: /SBDev
    port: 8001

这样POM中既不需要引入h2database也不需要在启动类上添加exclude = DataSourceAutoConfiguration.class,如果上述数据源配置无误且能正常链接,则启动SpringBoot的时候就可以正常启动

这里的配置最终会被读取到DataSourceAutoConfiguration中,然后再去创建数据源,看一下这个类的源码如下

/**
 * 自动配置数据源的类。根据应用程序的条件自动配置嵌入式数据库或连接池。
 * 
 * @AutoConfiguration before = {SqlInitializationAutoConfiguration.class} 指定此配置类在SqlInitializationAutoConfiguration之前加载
 * @ConditionalOnClass {DataSource.class, EmbeddedDatabaseType.class} 检查类路径中是否存在DataSource和EmbeddedDatabaseType类,以决定是否应该加载此配置
 * @ConditionalOnMissingBean type = {"io.r2dbc.spi.ConnectionFactory"} 如果应用程序中不存在ConnectionFactory bean,则加载此配置
 * @EnableConfigurationProperties {DataSourceProperties.class} 启用对DataSourceProperties属性的配置
 * @Import {DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class} 导入额外的配置类
 */
@AutoConfiguration(
    before = {SqlInitializationAutoConfiguration.class}
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class})
public class DataSourceAutoConfiguration {
    
    /**
     * 判断是否应该配置嵌入式数据库的条件类。
     * 它检查是否设置了spring.datasource.url属性,或者是否存在支持的连接池数据源。
     */
    static class EmbeddedDatabaseCondition extends SpringBootCondition {
        private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";
        private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();

        EmbeddedDatabaseCondition() {
        }

        /**
         * 根据当前环境和条件判断是否应该配置嵌入式数据库。
         * 
         * @param context 条件上下文,提供环境和类加载器等信息
         * @param metadata 注解类型元数据,用于获取注解参数等信息
         * @return 条件匹配的结果
         */
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource", new Object[0]);
            if (this.hasDataSourceUrlProperty(context)) {
                return ConditionOutcome.noMatch(message.because("spring.datasource.url is set"));
            } else if (this.anyMatches(context, metadata, new Condition[]{this.pooledCondition})) {
                return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
            } else {
                EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
                return type == null ? ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll()) : ConditionOutcome.match(message.found("embedded database").items(new Object[]{type}));
            }
        }

        /**
         * 检查是否设置了spring.datasource.url属性并且该属性有文本值。
         * 
         * @param context 条件上下文
         * @return 如果设置了有效的spring.datasource.url属性,则返回true;否则返回false
         */
        private boolean hasDataSourceUrlProperty(ConditionContext context) {
            Environment environment = context.getEnvironment();
            if (environment.containsProperty("spring.datasource.url")) {
                try {
                    return StringUtils.hasText(environment.getProperty("spring.datasource.url"));
                } catch (IllegalArgumentException var4) {
                }
            }

            return false;
        }
    }

    /**
     * 判断是否应该配置连接池数据源的条件类。
     */
    static class PooledDataSourceAvailableCondition extends SpringBootCondition {
        PooledDataSourceAvailableCondition() {
        }

        /**
         * 根据当前环境判断是否应该配置连接池数据源。
         * 
         * @param context 条件上下文
         * @param metadata 注解类型元数据
         * @return 条件匹配的结果
         */
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("PooledDataSource", new Object[0]);
            return DataSourceBuilder.findType(context.getClassLoader()) != null ? ConditionOutcome.match(message.foundExactly("supported DataSource")) : ConditionOutcome.noMatch(message.didNotFind("supported DataSource").atAll());
        }
    }

    /**
     * 管理连接池数据源配置的条件类。
     * 根据是否存在特定的DataSource类型或属性来决定是否应用配置。
     */
    static class PooledDataSourceCondition extends AnyNestedCondition {
        PooledDataSourceCondition() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }

        /**
         * 当存在支持的连接池数据源类型时触发的条件。
         */
        @Conditional({PooledDataSourceAvailableCondition.class})
        static class PooledDataSourceAvailable {
            PooledDataSourceAvailable() {
            }
        }

        /**
         * 当spring.datasource.type属性被设置时触发的条件。
         */
        @ConditionalOnProperty(
            prefix = "spring.datasource",
            name = {"type"}
        )
        static class ExplicitType {
            ExplicitType() {
            }
        }
    }

    /**
     * 配置连接池数据源的配置类。
     * 在不存在DataSource和XADataSource bean且满足其他条件的情况下激活。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }

        /**
         * 提供基于DataSourceProperties配置的JdbcConnectionDetails bean。
         * 
         * @param properties DataSource的属性
         * @return JdbcConnectionDetails的实例
         */
        @Bean
        @ConditionalOnMissingBean({JdbcConnectionDetails.class})
        PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
            return new PropertiesJdbcConnectionDetails(properties);
        }
    }

    /**
     * 配置嵌入式数据库的配置类。
     * 在不存在DataSource和XADataSource bean且满足其他条件的情况下激活。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({EmbeddedDatabaseCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({EmbeddedDataSourceConfiguration.class})
    protected static class EmbeddedDatabaseConfiguration {
        protected EmbeddedDatabaseConfiguration() {
        }
    }
}

代码中不难看出加载了配置类@EnableConfigurationProperties({DataSourceProperties.class}), 这是Spring暴露的配置,在看一下这个配置类的源码如下


/**
 * 数据源属性配置类,用于封装数据源相关的配置信息。
 * 通过@ConfigurationProperties注解,绑定到配置文件中以spring.datasource为前缀的配置项。
 */
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    private boolean generateUniqueName = true;
    private String name;
    private Class<? extends DataSource> type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private String jndiName;
    private EmbeddedDatabaseConnection embeddedDatabaseConnection;
    private Xa xa = new Xa();
    private String uniqueName;

    /**
     * 默认构造函数
     */
    public DataSourceProperties() {
    }

    /**
     * 实现BeanClassLoaderAware接口,设置类加载器
     * @param classLoader 类加载器
     */
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 实现InitializingBean接口,初始化数据源构建器
     * @throws Exception 初始化异常
     */
    public void afterPropertiesSet() throws Exception {
        if (this.embeddedDatabaseConnection == null) {
            this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
        }
    }

    /**
     * 初始化DataSourceBuilder,用于构建DataSource实例。
     * @return DataSourceBuilder实例
     */
    public DataSourceBuilder<?> initializeDataSourceBuilder() {
        return DataSourceBuilder.create(this.getClassLoader()).type(this.getType()).driverClassName(this.determineDriverClassName()).url(this.determineUrl()).username(this.determineUsername()).password(this.determinePassword());
    }

    /**
     * 获取是否生成唯一名称的属性。
     * @return true表示生成唯一名称,false表示不生成
     */
    public boolean isGenerateUniqueName() {
        return this.generateUniqueName;
    }

    /**
     * 设置是否生成唯一名称的属性。
     * @param generateUniqueName true表示生成唯一名称,false表示不生成
     */
    public void setGenerateUniqueName(boolean generateUniqueName) {
        this.generateUniqueName = generateUniqueName;
    }

    /**
     * 获取数据源名称。
     * @return 数据源名称
     */
    public String getName() {
        return this.name;
    }

    /**
     * 设置数据源名称。
     * @param name 数据源名称
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取数据源类型。
     * @return 数据源类型
     */
    public Class<? extends DataSource> getType() {
        return this.type;
    }

    /**
     * 设置数据源类型。
     * @param type 数据源类型
     */
    public void setType(Class<? extends DataSource> type) {
        this.type = type;
    }

    /**
     * 获取驱动类名称。
     * @return 驱动类名称
     */
    public String getDriverClassName() {
        return this.driverClassName;
    }

    /**
     * 设置驱动类名称。
     * @param driverClassName 驱动类名称
     */
    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    /**
     * 确定驱动类名称。
     * 如果已设置驱动类名称,则直接返回;否则根据URL确定驱动类名称。
     * @return 驱动类名称
     * @throws DataSourceBeanCreationException 如果无法确定驱动类名称,则抛出异常
     */
    public String determineDriverClassName() {
        if (StringUtils.hasText(this.driverClassName)) {
            Assert.state(this.driverClassIsLoadable(), () -> {
                return "Cannot load driver class: " + this.driverClassName;
            });
            return this.driverClassName;
        } else {
            String driverClassName = null;
            if (StringUtils.hasText(this.url)) {
                driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
            }

            if (!StringUtils.hasText(driverClassName)) {
                driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
            }

            if (!StringUtils.hasText(driverClassName)) {
                throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this, this.embeddedDatabaseConnection);
            } else {
                return driverClassName;
            }
        }
    }

    /**
     * 检查驱动类是否可加载。
     * @return true表示驱动类可加载,false表示不可加载
     */
    private boolean driverClassIsLoadable() {
        try {
            ClassUtils.forName(this.driverClassName, (ClassLoader)null);
            return true;
        } catch (UnsupportedClassVersionError var2) {
            throw var2;
        } catch (Throwable var3) {
            var3.printStackTrace();
            return false;
        }
    }

    /**
     * 获取URL。
     * @return JDBC URL
     */
    public String getUrl() {
        return this.url;
    }

    /**
     * 设置URL。
     * @param url JDBC URL
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * 确定URL。
     * 如果已设置URL,则直接返回;否则根据数据库名称确定URL。
     * @return JDBC URL
     * @throws DataSourceBeanCreationException 如果无法确定URL,则抛出异常
     */
    public String determineUrl() {
        if (StringUtils.hasText(this.url)) {
            return this.url;
        } else {
            String databaseName = this.determineDatabaseName();
            String url = databaseName != null ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
            if (!StringUtils.hasText(url)) {
                throw new DataSourceBeanCreationException("Failed to determine suitable jdbc url", this, this.embeddedDatabaseConnection);
            } else {
                return url;
            }
        }
    }

    /**
     * 确定数据库名称。
     * 如果需要生成唯一名称,则生成并返回唯一名称;否则根据name属性返回名称。
     * @return 数据库名称
     */
    public String determineDatabaseName() {
        if (this.generateUniqueName) {
            if (this.uniqueName == null) {
                this.uniqueName = UUID.randomUUID().toString();
            }

            return this.uniqueName;
        } else if (StringUtils.hasLength(this.name)) {
            return this.name;
        } else {
            return this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE ? "testdb" : null;
        }
    }

    /**
     * 获取用户名。
     * @return 用户名
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * 设置用户名。
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 确定用户名。
     * 如果已设置用户名,则直接返回;否则对于嵌入式数据库,返回"sa"。
     * @return 用户名
     */
    public String determineUsername() {
        if (StringUtils.hasText(this.username)) {
            return this.username;
        } else {
            return EmbeddedDatabaseConnection.isEmbedded(this.determineDriverClassName(), this.determineUrl()) ? "sa" : null;
        }
    }

    /**
     * 获取密码。
     * @return 密码
     */
    public String getPassword() {
        return this.password;
    }

    /**
     * 设置密码。
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 确定密码。
     * 如果已设置密码,则直接返回;否则对于嵌入式数据库,返回空字符串。
     * @return 密码
     */
    public String determinePassword() {
        if (StringUtils.hasText(this.password)) {
            return this.password;
        } else {
            return EmbeddedDatabaseConnection.isEmbedded(this.determineDriverClassName(), this.determineUrl()) ? "" : null;
        }
    }

    /**
     * 获取JNDI名称。
     * @return JNDI名称
     */
    public String getJndiName() {
        return this.jndiName;
    }

    /**
     * 设置JNDI名称。
     * @param jndiName JNDI名称
     */
    public void setJndiName(String jndiName) {
        this.jndiName = jndiName;
    }

    /**
     * 获取嵌入式数据库连接类型。
     * @return 嵌入式数据库连接类型
     */
    public EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
        return this.embeddedDatabaseConnection;
    }

    /**
     * 设置嵌入式数据库连接类型。
     * @param embeddedDatabaseConnection 嵌入式数据库连接类型
     */
    public void setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection embeddedDatabaseConnection) {
        this.embeddedDatabaseConnection = embeddedDatabaseConnection;
    }

    /**
     * 获取类加载器。
     * @return 类加载器
     */
    public ClassLoader getClassLoader() {
        return this.classLoader;
    }

    /**
     * 获取Xa配置。
     * @return Xa配置
     */
    public Xa getXa() {
        return this.xa;
    }

    /**
     * 设置Xa配置。
     * @param xa Xa配置
     */
    public void setXa(Xa xa) {
        this.xa = xa;
    }

    /**
     * Xa配置类,用于存储XA数据源的相关配置。
     */
    public static class Xa {
        private String dataSourceClassName;
        private Map<String, String> properties = new LinkedHashMap();

        /**
         * 获取数据源类名称。
         * @return 数据源类名称
         */
        public String getDataSourceClassName() {
            return this.dataSourceClassName;
        }

        /**
         * 设置数据源类名称。
         * @param dataSourceClassName 数据源类名称
         */
        public void setDataSourceClassName(String dataSourceClassName) {
            this.dataSourceClassName = dataSourceClassName;
        }

        /**
         * 获取属性配置。
         * @return 属性配置
         */
        public Map<String, String> getProperties() {
            return this.properties;
        }

        /**
         * 设置属性配置。
         * @param properties 属性配置
         */
        public void setProperties(Map<String, String> properties) {
            this.properties = properties;
        }
    }

    /**
     * 数据源创建异常类,用于处理数据源创建过程中的异常。
     */
    static class DataSourceBeanCreationException extends BeanCreationException {
        private final DataSourceProperties properties;
        private final EmbeddedDatabaseConnection connection;

        /**
         * 构造函数。
         * @param message 异常信息
         * @param properties 数据源属性
         * @param connection 嵌入式数据库连接类型
         */
        DataSourceBeanCreationException(String message, DataSourceProperties properties, EmbeddedDatabaseConnection connection) {
            super(message);
            this.properties = properties;
            this.connection = connection;
        }

        /**
         * 获取数据源属性。
         * @return 数据源属性
         */
        DataSourceProperties getProperties() {
            return this.properties;
        }

        /**
         * 获取嵌入式数据库连接类型。
         * @return 嵌入式数据库连接类型
         */
        EmbeddedDatabaseConnection getConnection() {
            return this.connection;
        }
    }
}

代码中不难看出,要求对应的配置项以spring.datasource开头,上一段源码中有一段加入各种数据源配置的地方

 @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})

再深入看一下源码,以第一个数据源配置类为例

/**
 * 数据源配置类,用于根据不同的数据源类型创建对应的数据源实例。
 */
abstract class DataSourceConfiguration {
    
    /**
     * 根据连接详情和数据源类型创建数据源实例。
     * 
     * @param connectionDetails 连接详情,包含 JDBC URL、驱动类名、用户名和密码等信息。
     * @param type 数据源的类类型。
     * @param classLoader 类加载器。
     * @param <T> 数据源类型。
     * @return 创建的数据源实例。
     */
    private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type, ClassLoader classLoader) {
        // 使用 DataSourceBuilder 创建并配置数据源实例,然后返回。
        return DataSourceBuilder.create(classLoader).type(type).driverClassName(connectionDetails.getDriverClassName()).url(connectionDetails.getJdbcUrl()).username(connectionDetails.getUsername()).password(connectionDetails.getPassword()).build();
    }

    /**
     * 配置通用的数据源 bean,当不存在自定义数据源 bean 且配置了 spring.datasource.type 时使用。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        /**
         * 根据连接属性和连接详情创建并配置数据源 bean。
         * 
         * @param properties 数据源属性。
         * @param connectionDetails 连接详情。
         * @return 配置后的数据源实例。
         */
        @Bean
        DataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
            // 调用 createDataSource 方法创建并返回数据源实例。
            return (DataSource)DataSourceConfiguration.createDataSource(connectionDetails, properties.getType(), properties.getClassLoader());
        }
    }

    /**
     * 配置 Oracle UCP 数据源 bean,当类路径下存在 PoolDataSourceImpl 类且未定义 DataSource bean 时使用。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({PoolDataSourceImpl.class, OracleConnection.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "oracle.ucp.jdbc.PoolDataSource",
        matchIfMissing = true
    )
    static class OracleUcp {
        /**
         * 创建并配置 Oracle UCP 数据源 bean。
         * 
         * @param properties 数据源属性。
         * @param connectionDetails 连接详情。
         * @return 配置后的 Oracle UCP 数据源实例。
         * @throws SQLException 如果配置数据源时发生错误。
         */
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.oracleucp"
        )
        PoolDataSourceImpl dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) throws SQLException {
            PoolDataSourceImpl dataSource = (PoolDataSourceImpl)DataSourceConfiguration.createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader());
            // 如果设置了数据源名称,则应用到 Oracle UCP 数据源。
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setConnectionPoolName(properties.getName());
            }
            return dataSource;
        }
    }

    /**
     * 配置 DBCP2 数据源 bean,当类路径下存在 BasicDataSource 类且未定义 DataSource bean 时使用。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({BasicDataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "org.apache.commons.dbcp2.BasicDataSource",
        matchIfMissing = true
    )
    static class Dbcp2 {
        /**
         * 创建并配置 DBCP2 数据源 bean。
         * 
         * @param properties 数据源属性。
         * @param connectionDetails 连接详情。
         * @return 配置后的 DBCP2 数据源实例。
         */
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.dbcp2"
        )
        BasicDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
            Class<? extends DataSource> dataSourceType = BasicDataSource.class;
            return (BasicDataSource)DataSourceConfiguration.createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());
        }
    }

    /**
     * 配置 HikariCP 数据源 bean,当类路径下存在 HikariDataSource 类且未定义 DataSource bean 时使用。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({HikariDataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "com.zaxxer.hikari.HikariDataSource",
        matchIfMissing = true
    )
    static class Hikari {
        /**
         * 创建并配置 HikariCP 数据源 bean。
         * 
         * @param properties 数据源属性。
         * @param connectionDetails 连接详情。
         * @return 配置后的 HikariCP 数据源实例。
         */
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.hikari"
        )
        HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
            HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(connectionDetails, HikariDataSource.class, properties.getClassLoader());
            // 如果设置了数据源名称,则应用到 HikariCP 数据源。
            if (StringUtils.hasText(properties.getName())) {
                dataSource.setPoolName(properties.getName());
            }
            return dataSource;
        }
    }

    /**
     * 配置 Tomcat 数据源 bean,当类路径下存在 org.apache.tomcat.jdbc.pool.DataSource 类且未定义 DataSource bean 时使用。
     */
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({org.apache.tomcat.jdbc.pool.DataSource.class})
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"},
        havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
        matchIfMissing = true
    )
    static class Tomcat {
        /**
         * 创建并配置 Tomcat 数据源 bean。
         * 
         * @param properties 数据源属性。
         * @param connectionDetails 连接详情。
         * @return 配置后的 Tomcat 数据源实例。
         */
        @Bean
        @ConfigurationProperties(
            prefix = "spring.datasource.tomcat"
        )
        org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
            Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;
            org.apache.tomcat.jdbc.pool.DataSource dataSource = (org.apache.tomcat.jdbc.pool.DataSource)DataSourceConfiguration.createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());
            DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(connectionDetails.getJdbcUrl());
            String validationQuery = databaseDriver.getValidationQuery();
            // 如果存在验证查询语句,则启用borrow时的验证并设置验证查询语句。
            if (validationQuery != null) {
                dataSource.setTestOnBorrow(true);
                dataSource.setValidationQuery(validationQuery);
            }
            return dataSource;
        }
    }
}

如此Spring Boot就可以通过我们的配置,将数据源创建出来了,其他的也是同理,比如Spring MVC、Redis等等,近一步测试一下,先写一个控制器, 代码如下

package com.sbdev.controller.db;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/role")
public class DataSourceController {

    // 注入JdbcTemplate,它由Spring Boot自动创建不需要我们干预
    @Autowired
    private JdbcTemplate jdbcTemplate = null;

    /**
     * 获取角色
     * @param id 角色编号
     * @return 角色信息
     */
    @GetMapping("/info/{id}")
    public Map<String, Object> getRole(@PathVariable("id") Long id) {
        System.out.println("DataSource类型:" + jdbcTemplate.getDataSource().getClass().getName());
        Map<String, Object> roleMap = null;
        String sql = "select id, role_name, note from t_role where id = ?";
        roleMap = jdbcTemplate.queryForMap(sql, id);
        return roleMap;
    }
}

然后访问地址http://localhost:8001/SBDev/role/info/2即可
在这里插入图片描述
Spring Boot默认使用Hikari数据源,有时候我们想切换不同的数据源,Spring Boot也给予了支持,例如另一个数据源是DBCP2,先在POM添加该依赖

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.12.0</version>
        </dependency>

接着修改application.yml文件数据库的配置,如下所示

# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: 
  - SBDev

spring:
  # 数据源配置,用于连接和管理数据库
  datasource:
    # 数据库驱动类名,指定连接MySQL数据库所需的驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    # 数据库用户名,用于身份验证和授权
    username: root
    # 数据库密码,与用户名配合用于身份验证和授权
    password: Ms123!@#
    # 数据源类型,使用Apache Commons DBCP2提供的基本数据源
    type: org.apache.commons.dbcp2.BasicDataSource
    # DBCP2数据源特定配置,用于管理连接池的大小和行为
    dbcp2:
      # 最大空闲连接数,超过该数目的空闲连接将被关闭
      max-idle: 20
      # 等待连接池分配连接的最大时间,超过该时间仍未获取到连接将抛出异常
      max-wait-millis: 5000
      # 连接池允许的最大连接数,超过该数目将无法获取新的连接
      max-total: 50
      # 最小空闲连接数,连接池会维护至少这么多的空闲连接

# 服务器配置,用于设置服务器端口和上下文路径
server:
  servlet:
    # 应用程序上下文路径,用于区分不同的Spring Boot应用或服务
    context-path: /SBDev
  # 服务器端口,指定应用监听的端口号
  port: 8001

Spring Boot整合MyBatis

在Spring Boot中整合MyBatis和传统方式差不多,只是部分MyBatis组件可以在SpringBoot配置文件中进行配置,且常见的类都会自动初始化,不需要编写,

添加依赖
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
建表
use ssm;

Create Table t_user(
	id int(12) not null auto_increment comment '主键',
	username varchar(255) not null comment '用户名',
	password varchar(255) not null comment '密码',
    sex int(3) not null default 0 comment '性别',
    note varchar(255)  comment '备注',
	primary key(id),
    CHECK (sex in (0,1))
);

insert into t_user(username,password,sex,note) values('admin','123456',0,'管理员');
insert into t_user(username,password,sex,note) values('user','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user2','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user3','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user4','123456',1,'普通用户');
写POJO
package com.sbdev.dic;


public enum SexEnum {

    MALE(0, "男"),
    FEMALE(1, "女");

    private Integer id;
    private String value;
    SexEnum(Integer id, String value) {
        this.id = id;
        this.value = value;
    }

    /**
     * 获取根据编号获取性别枚举
     * @param id 编号
     * @return 枚举
     */
    public static SexEnum getSexEnum(Integer id) {
        for (SexEnum sex : SexEnum.values()) {
            if (sex.getId().equals(id)) {
                return  sex;
            }
        }
        return null;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

package com.sbdev.pojo;

import com.sbdev.dic.SexEnum;
import org.apache.ibatis.type.Alias;

import java.io.Serializable;

@Alias("user")
public class User implements Serializable {
    private static final long serialVersionUID = 2386785787854557L;
    private Long id;
    private String userName;
    private SexEnum sex;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public SexEnum getSex() {
        return sex;
    }

    public void setSex(SexEnum sex) {
        this.sex = sex;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}
写映射文件
<?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="com.learn.ssm.chapter25.dao.UserDao">
    <select id="getUser" parameterType="long" resultType="user">
        select id, user_name as userName, sex, note from t_user where id = #{id}
    </select>

    <insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
        insert  into t_user(user_name, sex, note) values (#{userName}, #{sex}, #{note})
    </insert>
</mapper>
定义DAO接口层
package com.sbdev.dao;


import com.sbdev.pojo.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao {

    public User getUser(Long id);

    public Integer insertUser(User user);
}

编写TypeHandler

因为用户的性别是个枚举,为了更方便的使用它,需要编写一个TypeHandler

package com.sbdev.type.handler;

import com.sbdev.dic.SexEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 性别枚举类型处理器,用于MyBatis中将数据库中的整型数据与SexEnum枚举类型相互转换。
 * 使用@MappedJdbcTypes和@MappedTypes注解分别指定了对应的JDBC类型和Java类型。
 */
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes({SexEnum.class})
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {

    /**
     * 设置非空参数。
     * 将枚举类型SexEnum的id值设置到PreparedStatement中,对应数据库中的整型字段。
     * @param ps      PreparedStatement对象
     * @param idx     参数索引
     * @param sex     性别枚举值
     * @param jdbcType JDBC类型
     * @throws SQLException 如果设置参数时发生错误
     */
    @Override
    public void setNonNullParameter(
            PreparedStatement ps, int idx, SexEnum sex, JdbcType jdbcType) throws SQLException {
        ps.setInt(idx, sex.getId());
    }

    /**
     * 从ResultSet中获取非空结果。
     * 根据列名获取整型值,并将其转换为SexEnum枚举类型。
     * @param rs      ResultSet对象
     * @param columnName 列名
     * @return 性别枚举值
     * @throws SQLException 如果获取结果时发生错误
     */
    @Override
    public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Integer id = rs.getInt(columnName);
        return SexEnum.getSexEnum(id);
    }

    /**
     * 从ResultSet中获取非空结果。
     * 根据索引获取整型值,并将其转换为SexEnum枚举类型。
     * @param rs      ResultSet对象
     * @param idx     列索引
     * @return 性别枚举值
     * @throws SQLException 如果获取结果时发生错误
     */
    @Override
    public SexEnum getNullableResult(ResultSet rs, int idx) throws SQLException {
        Integer id = rs.getInt(idx);
        return SexEnum.getSexEnum(id);
    }

    /**
     * 从CallableStatement中获取非空结果。
     * 根据索引获取整型值,并将其转换为SexEnum枚举类型。
     * @param cs      CallableStatement对象
     * @param idx     列索引
     * @return 性别枚举值
     * @throws SQLException 如果获取结果时发生错误
     */
    @Override
    public SexEnum getNullableResult(CallableStatement cs, int idx) throws SQLException {
        Integer id = cs.getInt(idx);
        return SexEnum.getSexEnum(id);
    }
}

配置MyBatis

在传统的MyBatis使用中,我们还需要XML配置文件或者使用Java代码继承配置类进行配置,在SpringBoot中,通过application.yml进行配置即可

# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDev

spring:
  # 数据源配置,用于连接和管理数据库
  datasource:
    # 数据库驱动类名,指定连接MySQL数据库所需的驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    # 数据库用户名,用于身份验证和授权
    username: root
    # 数据库密码,与用户名配合用于身份验证
    password: Ms123!@#
    # 数据源类型,使用Apache Commons DBCP2提供的基本数据源
    type: org.apache.commons.dbcp2.BasicDataSource
    # DBCP2特定的配置,用于管理连接池的大小和行为
    dbcp2:
      # 最大空闲连接数,超过该数目的空闲连接将被关闭
      max-idle: 20
      # 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常
      max-wait-millis: 5000
      # 连接池允许的最大连接数,超过该数目将拒绝新的连接请求
      max-total: 50


# 服务器配置,用于设置应用的访问路径和端口
server:
  # 应用程序的上下文路径,用于区分不同的服务或模块
  servlet:
    context-path: /SBDev
  # 服务器端口,指定应用监听的网络端口号
  port: 8001


# mybatis配置项
mybatis:
  # 映射文件路径
  mapper-locations: classpath:mapper/*.xml
  # TypeHandler扫描包
  type-handlers-package: com.sbdev.type.handler
  # 扫描别名
  type-aliases-package: com.sbdev.pojo

logging:
  level:
    # 日志级别
    root: DEBUG
开发业务层
package com.sbdev.service;

import com.sbdev.pojo.User;

public interface UserService {

    public User getUser(Long id);

    public Integer insertUser(User user);

}

package com.sbdev.service.impl;

import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao  = null;

    @Override
    @Cacheable(value="redisCache", key="'redis_user_'+#id")
    public User getUser(Long id) {
        return userDao.getUser(id);
    }
    @Override
    // 事务管理器由Spring Boot自动装配,无需自己配置
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }
}
开发控制器
package com.sbdev.controller;


import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // REST风格网站
@RequestMapping("/user")
public class UserController {
    // 用户服务接口
    @Autowired
    private UserService userService = null;

    /**
     * 获取用户信息
     * @param id 用户编号
     * @return 用户信息
     */
    @GetMapping("/info/{id}")
    public User getUser(@PathVariable("id") Long id) {
        User user = userService.getUser(id);
        return user;
    }

    @GetMapping("/addition/{id}")
    public User insertUser(@PathVariable("id") Long id) {
        User user = new User();
        user.setSex(SexEnum.getSexEnum(id.intValue()%2));
        user.setUserName("user_name_" + id);
        user.setNote("note_" + id);
        userService.insertUser(user);
        return user;
    }

    @GetMapping("/print/{user}")
    public User print(User user) {
        return user;
    }
}

配置SpringBoot启动
/**
 * 应用程序的入口点。
 * 使用@SpringBootApplication注解标记这个类作为Spring Boot应用程序的起点。
 * scanBasePackages属性指定了Spring应用程序上下文应该扫描的包,即在这个包及其子包下寻找组件(如控制器、服务、配置类等)。
 * 这里指定的包是"com.sbdev",意味着所有在这个包下面的组件都会被Spring Boot自动识别和管理。
 */
@SpringBootApplication(
        scanBasePackages = "com.sbdev"
)
/**
 * 配置MyBatis的Mapper扫描器。
 * 通过此注解,Spring Boot将自动扫描指定包下的所有Mapper接口,并将其注册到MyBatis的SqlSessionFactory或SqlSessionTemplate中。
 * @param basePackages 指定需要扫描的Mapper接口所在的包。Spring Boot会递归扫描指定包及其子包下的所有接口。
 * @param annotationClass 指定需要扫描的接口上应存在的注解。这里指定为@Mapper,意味着只有标注了@Mapper注解的接口才会被扫描和处理。
 * @param sqlSessionFactoryRef 指定SqlSessionFactory的Bean名称。扫描到的Mapper接口将使用这个SqlSessionFactory来创建SqlSession。
 * @param sqlSessionTemplateRef 指定SqlSessionTemplate的Bean名称。扫描到的Mapper接口将使用这个SqlSessionTemplate来执行SQL操作。
 */
@MapperScan(
        basePackages = "com.sbdev",
        annotationClass = Mapper.class,
        sqlSessionFactoryRef = "sqlSessionFactory",
        sqlSessionTemplateRef = "sqlSessionTemplate"
)

@EnableCaching
public class SbDevApplication {

    public static void main(String[] args) {
        SpringApplication.run(SbDevApplication.class, args);
    }

SpringBoot启动类配置,注释写的比较清楚

注意这个配置sqlSessionFactoryRef = "sqlSessionFactory",sqlSessionTemplateRef = "sqlSessionTemplate",在这里可以删除它们,因为在项目中不存在多个SqlSessionFactorySqlSessionTemplate,但是从头到尾都没有sqlSessionFactorysqlSessionTemplate相关的配置或定义,那在这里却可以直接配置的原因便是当我们配置了数据源javax.sql.DataSource后,Spring Boot就会自动创建数据源的Bean,并且将它装配到Spring IoC容器中,而这个过程无需开发,也是Spring Boot的特色

插件
package com.sbdev.plugin;


import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;
/**
 * 消费时间插件,用于拦截MyBatis的Executor执行查询操作,计算查询操作的消耗时间。
 * 该插件通过实现Interceptor接口,利用MyBatis的插件机制,在查询执行前后插入计时逻辑。
 */
@Intercepts(
        @Signature(
                type = Executor.class,
                method = "query",
                args = {
                        MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
                }))
public class ConsumptionTimePlugin implements Interceptor {

    /**
     * 对Executor的query方法进行拦截。
     * 在执行查询操作前后记录时间,以计算查询操作的耗时,并输出到控制台。
     *
     * @param invocation 查询操作的调用信息,包含执行查询所需的所有参数。
     * @return 查询操作的结果对象。
     * @throws Throwable 查询操作中可能抛出的任何异常。
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 记录查询开始时间
        long start = System.currentTimeMillis();
        // 执行查询操作
        Object returnObj = invocation.proceed();
        // 记录查询结束时间
        long end = System.currentTimeMillis();
        // 计算并输出查询耗时
        System.out.println("耗时【" + (end - start)+"】毫秒");
        return returnObj;
    }

    /**
     * 为指定的目标对象创建一个插件代理。
     * 该方法用于实现插件的包装逻辑,将当前插件应用于目标对象,以拦截目标对象的方法调用。
     *
     * @param target 被插件化的对象,即MyBatis的Executor实例。
     * @return 包装了当前插件的目标对象的代理实例。
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 设置插件的属性。
     * 该方法用于接收从配置文件中读取的插件属性,当前插件未使用任何属性,因此该方法为空实现。
     *
     * @param properties 插件的属性配置。
     */
    @Override
    public void setProperties(Properties properties) {
        // 该插件不使用任何属性,因此该方法为空实现
    }
}

这是一个MyBatis插件,可以用来监控执行查询SQL消耗的时间,因为插件相对独立,可以将其配置到MyBatis自身的配置文件中,在resources目录下创建mybatis-config.xml,来配置插件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置文件的根元素,用于定义MyBatis的全局属性、类型别名、映射器等 -->
<configuration>
    <!-- 插件配置,用于注册自定义的插件 -->
    <plugins>
        <!-- 注册一个插件,该插件实现了com.sbdev.plugin.ConsumptionTimePlugin接口 -->
        <!-- 该插件用于记录方法的执行时间,实现性能监控 -->
        <plugin interceptor="com.sbdev.plugin.ConsumptionTimePlugin"/>
    </plugins>
</configuration>

将MyBatis配置文件加到SpringBoot的配置里,修改application.yml如下

# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDev

spring:
  # 数据源配置,用于连接和管理数据库
  datasource:
    # 数据库驱动类名,指定连接MySQL数据库所需的驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    # 数据库用户名,用于身份验证和授权
    username: root
    # 数据库密码,与用户名配合用于身份验证
    password: Ms123!@#
    # 数据源类型,使用Apache Commons DBCP2提供的基本数据源
    type: org.apache.commons.dbcp2.BasicDataSource
    # DBCP2特定的配置,用于管理连接池的大小和行为
    dbcp2:
      # 最大空闲连接数,超过该数目的空闲连接将被关闭
      max-idle: 20
      # 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常
      max-wait-millis: 5000
      # 连接池允许的最大连接数,超过该数目将拒绝新的连接请求
      max-total: 50


# 服务器配置,用于设置应用的访问路径和端口
server:
  # 应用程序的上下文路径,用于区分不同的服务或模块
  servlet:
    context-path: /SBDev
  # 服务器端口,指定应用监听的网络端口号
  port: 8001


# mybatis配置项
mybatis:
  # 映射文件路径
  mapper-locations: classpath:mapper/*.xml
  # TypeHandler扫描包
  type-handlers-package: com.sbdev.type.handler
  # 扫描别名
  type-aliases-package: com.sbdev.pojo
  # 配置MyBatis配置文件的位置
  # 使用classpath指定配置文件位于类路径下,便于资源的统一管理和访问
  config-location: classpath:mybatis-config.xml


logging:
  level:
    # 日志级别
    root: DEBUG

实际上还有很多配置项,可以自行研究,这样配置完,Spring Boot就会启用这个插件了
在这里插入图片描述

在这里插入图片描述

数据库事务

在Spring Boot初始化数据源的时候,会同时初始化对应的数据库事务管理器,因此不需要配置任何数据库事务的内容可以直接使用,例如如下代码

package com.sbdev.service.impl;

import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao  = null;

    @Override
    @Cacheable(value="redisCache", key="'redis_user_'+#id")
    public User getUser(Long id) {
        return userDao.getUser(id);
    }

    @Override
    // 事务管理器由Spring Boot自动装配,无需自己配置
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }
}

为了方便使用Spring Boot还提供了默认隔离级别的配置,如下所示

spring:
  # 数据源配置,用于连接和管理数据库
  datasource:
    # 配置Hikari连接池的事务隔离级别为读未提交
    # 这一设置影响使用Hikari连接池的数据库连接的默认事务隔离行为
    hikari:
      transaction-isolation: 2
    # -1:默认隔离级别
    # 1:读未提交
    # 2:读已提交
    # 4:可重复读
    # 8:串行化
    # 配置Tomcat连接池的默认事务隔离级别为读未提交
    # 此设置适用于所有通过Tomcat连接池获取的数据库连接
    tomcat:
      default-transaction-isolation: 2

    # 数据库驱动类名,指定连接MySQL数据库所需的驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    # 数据库用户名,用于身份验证和授权
    username: root
    # 数据库密码,与用户名配合用于身份验证
    password: Ms123!@#
    # 数据源类型,使用Apache Commons DBCP2提供的基本数据源
    type: org.apache.commons.dbcp2.BasicDataSource
    # DBCP2特定的配置,用于管理连接池的大小和行为
    dbcp2:
      # 最大空闲连接数,超过该数目的空闲连接将被关闭
      max-idle: 20
      # 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常
      max-wait-millis: 5000
      # 连接池允许的最大连接数,超过该数目将拒绝新的连接请求
      max-total: 50
      # 设置默认事务隔离级别为二级隔离
      default-transaction-isolation: 2

Spring Boot和Spring MVC

在大部分情况下,在Spring Boot中使用Spring MVC的方法和传统Spring MVC并无太大的不同,但又一些特殊用法

使用WebMvcConfigurer接口

通常我们可以通过实现WebMvcConfiguration接口来自定义Spring MVC的组件,例如需要开发一个用户拦截器,如下代码所示

/**
 * 用户拦截器类,用于在请求处理的不同阶段执行自定义逻辑。
 * 实现了Spring MVC的HandlerInterceptor接口。
 */
package com.sbdev.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class UserInterceptor implements HandlerInterceptor {

    /**
     * 在请求处理之前执行的逻辑。
     * 该方法用于进行权限检查或初始化一些资源。
     * @param request  当前请求的HttpServletRequest对象
     * @param response 当前请求的HttpServletResponse对象
     * @param handler  将要处理请求的目标对象
     * @return true表示继续处理请求,false表示中断请求处理
     * @throws Exception 如果在预处理阶段发生异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    /**
     * 在请求处理完成后,但在视图渲染之前执行的逻辑。
     * 该方法可用于进行一些数据处理或清理工作。
     * @param request  当前请求的HttpServletRequest对象
     * @param response 当前请求的HttpServletResponse对象
     * @param handler  处理请求的目标对象
     * @param modelAndView 视图模型对象,可能为null,表示不返回视图
     * @throws Exception 如果在后处理阶段发生异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    /**
     * 在整个请求处理完成后,包括视图渲染,执行的逻辑。
     * 该方法可用于进行一些资源的释放或日志记录等清理工作。
     * @param request  当前请求的HttpServletRequest对象
     * @param response 当前请求的HttpServletResponse对象
     * @param handler  处理请求的目标对象
     * @param ex       在请求处理过程中抛出的异常,可能为null
     * @throws Exception 如果在完成处理阶段发生异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}
package com.sbdev.config;

import com.sbdev.interceptor.UserInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web配置类,用于自定义Spring MVC的配置。
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器配置。
     * @param registry 拦截器注册表,用于注册和管理拦截器。
     *                 这里通过添加UserInterceptor拦截器并指定拦截路径为/user/**,
     *                 实现对用户相关请求的拦截处理。
     */
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加UserInterceptor拦截器
        registry.addInterceptor(new UserInterceptor())
                // 指定拦截器拦截的URL路径
                .addPathPatterns("/user/**");
    }
}

这样就可以配置各类Spring MVC组件了

使用SpringBoot的Spring MVC配置

# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDev

spring:
  # 数据源配置,用于连接和管理数据库
  datasource:
    # 配置Hikari连接池的事务隔离级别为读未提交
    # 这一设置影响使用Hikari连接池的数据库连接的默认事务隔离行为
    hikari:
      transaction-isolation: 2
    # -1:默认隔离级别
    # 1:读未提交
    # 2:读已提交
    # 4:可重复读
    # 8:串行化
    # 配置Tomcat连接池的默认事务隔离级别为读未提交
    # 此设置适用于所有通过Tomcat连接池获取的数据库连接
    tomcat:
      default-transaction-isolation: 2

    # 数据库驱动类名,指定连接MySQL数据库所需的驱动类
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数
    url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false
    # 数据库用户名,用于身份验证和授权
    username: root
    # 数据库密码,与用户名配合用于身份验证
    password: Ms123!@#
    # 数据源类型,使用Apache Commons DBCP2提供的基本数据源
    type: org.apache.commons.dbcp2.BasicDataSource
    # DBCP2特定的配置,用于管理连接池的大小和行为
    dbcp2:
      # 最大空闲连接数,超过该数目的空闲连接将被关闭
      max-idle: 20
      # 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常
      max-wait-millis: 5000
      # 连接池允许的最大连接数,超过该数目将拒绝新的连接请求
      max-total: 50
      # 设置默认事务隔离级别为二级隔离
      default-transaction-isolation: 2

  # 配置 MVC 框架的相关设置
  mvc:
    # 配置用户服务的 Servlet 信息
    servlet:
      # 定义 Servlet 的访问路径
      path: /user
      # 设置 Servlet 在应用启动时加载的顺序
      load-on-startup: 1
    # 配置视图解析的相关设置
    view:
      # 定义视图文件的后缀名
      suffix: .jsp
      # 定义视图文件的目录前缀
      prefix: /WEB-INF/jsp/

  # 配置 Web 应用的国际化设置
  web:
    # 设置固定的地域解析器
    locale-resolver: fixed
    # 指定应用的默认地域为中文(中国)
    locale: zh_CN

  # 配置 Jackson 的相关设置,用于序列化和反序列化日期时间
  jackson:
    # 定义日期时间的格式化样式
    date-format: yyyy-MM-dd HH:mm:ss




# 服务器配置,用于设置应用的访问路径和端口
server:
  # 应用程序的上下文路径,用于区分不同的服务或模块
  servlet:
    context-path: /SBDev
  # 服务器端口,指定应用监听的网络端口号
  port: 8001


# mybatis配置项
mybatis:
  # 映射文件路径
  mapper-locations: classpath:mapper/*.xml
  # TypeHandler扫描包
  type-handlers-package: com.sbdev.type.handler
  # 扫描别名
  type-aliases-package: com.sbdev.pojo
  # 配置MyBatis配置文件的位置
  # 使用classpath指定配置文件位于类路径下,便于资源的统一管理和访问
  config-location: classpath:mybatis-config.xml



logging:
  level:
    # 日志级别
    root: DEBUG

在IDEA里双击Shift键,搜MybatisProperties或者搜WebMvcProperties能看到如下内容,都是Spring Boot配置文件中的配置项
在这里插入图片描述
在这里插入图片描述

使用转换器

定义转换器,例如用户POJO,可以约定浏览器提交的格式为{id}-{userName}-{sex}-{note}可以使用Converter机制处理

package com.sbdev.comverter;

import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
public class UserConverter implements Converter<String, User> {


    /**
     * 约定提交格式“{id}-{userName}-{sex}-{note}”
     * @param source 源字符串
     * @return 用户对象
     */
    @Override
    public User convert(String source) {
        if (source == null) {
            return null;
        }
        String []arr = source.split("-");
        if (arr.length != 4) { // 不符合格式
            return null;
        }
        User user = new User();
        user.setId(Long.parseLong(arr[0]));
        user.setUserName(arr[1]);
        user.setSex(SexEnum.getSexEnum(Integer.parseInt(arr[2])));
        user.setNote(arr[3]);
        return user;
    }
}

这里标注了@Component,这意味着它将被扫描装配到Spring IoC容器中,然后可以通过控制器来进行测试

    @GetMapping("/print/{user}")
    public User print(User user) {
        return user;
    }

然后在浏览器中输入输入地址user/print/1-username-0-note测试转换器是否生效即可,这里我们并没有给Spring MVC注册UserConverter,但是Spring MVC还是使用了UserConverter去转换参数,这事因为Spring Boot已经为我们做了注册,但前提是需要将它们装配到Spring IoC容器中去

在Spring MVC的配置类WebMvcAutoConfiguration中调用了ApplicationConversionService的静态addBeans方法,源码如下

    /**
     * 将豆类工厂中的转换器、格式化器、解析器注册到格式化器注册表中。
     * 此方法旨在支持自动注册由Spring Bean工厂提供的各种转换器和格式化器,以增强系统的数据处理能力。
     * @param registry 格式化器注册表,用于接收和管理转换器、格式化器、解析器。
     * @param beanFactory Spring的Bean工厂,用于获取所有实现了转换器、格式化器、解析器接口的Bean实例。
     */
    public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
        // 使用LinkedHashSet来保存转换器、格式化器、解析器实例,以保持注册顺序。
        Set<Object> beans = new LinkedHashSet<>();
        // 分别获取并合并GenericConverter、Converter、Printer、Parser类型的Bean实例。
        beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
        beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
        beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
        beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
        // 遍历所有获取到的Bean实例。
        Iterator var3 = beans.iterator();
        while(var3.hasNext()) {
            Object bean = var3.next();
            // 根据实例类型,分别注册到对应的格式化器注册表中。
            // 这里使用了Java的实例类型检查来决定注册哪种类型的实例。
            if (bean instanceof GenericConverter genericConverter) {
                registry.addConverter(genericConverter);
            } else if (bean instanceof Converter<?, ?> converter) {
                registry.addConverter(converter);
            } else if (bean instanceof Formatter<?> formatter) {
                registry.addFormatter(formatter);
            } else if (bean instanceof Printer<?> printer) {
                registry.addPrinter(printer);
            } else if (bean instanceof Parser<?> parser) {
                registry.addParser(parser);
            }
        }
    }

SpringBoot会将所有在Spring IoC容器中的Converter、GenericConverter、Formatter都注册到Spring MVC的转换机制中,所以即使我们没有显示的注册,Spring MVC也能发现我们自定义的转换器

Spring Boot 使用Redis

使用Redis需要添加相关依赖

        <!-- 添加Tomcat-JDBC依赖 -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>

        <!-- 引入Spring Boot的Redis支持,但排除Lettuce客户端 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <!--不依赖Redis的异步客户端lettuce-->
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入Jedis作为Redis的同步客户端 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

Spring Boot默认依赖Lettuce客户端,但这里排除了它,因为Lettuce是一个可伸缩线程安全的Redis客户端,多个线程可以共享一个Redis连接,所以会牺牲一部分性能,但一般来说缓存对线程安全的需求并不高,更注重性能,而Jedis是一种多线程非安全的客户端,性能更好

配置Redis

  data:
    redis:
      host: 127.0.0.1
      port: 6379
    jedis:
      pool:
        # 最大连接数
        max-active: 20
        # 最大等待时间
        max-wait: 2000ms
        # 最大空闲连接数
        max-idle: 10
        # 最小空闲等待连接数
        min-idle: 5
    #    # 哨兵模式配置
    #    sentinel:
    #      # 主服务器
    #      master: 192.168.80.130:26379
    #      # 节点
    #      nodes: 192.168.80.131:26379, 192.168.80.132:26379
    #    # 集群配置
    #    cluster:
    #      # 集群节点
    #      nodes: 192.168.80.133:7001, 192.168.80.133:7002, 192.168.80.133:7003, 192.168.80.133:7004, 192.168.80.133:7005, 192.168.80.133:7006
    #      # 单一连接最大转向次数
    #      max-redirects: 10

在Spring Boot中自动配置类为RedisAutoConfiguration属性类为RedisProperties,更多的配置可以在这里边找到配置好后便可以使用Redis了

package com.sbdev.controller;


import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/redis")
public class RedisController {
    // StringRedisTemplate是RedisTemplate的子类,但是该属性名“redisTemplate”和Bean名称一致,
    // 因此可以使用@Autowired注入,如果属性名称不为“redisTemplate”,则注入失败,抛出异常
    @Autowired
//    private RedisTemplate<Object, Object> redisTemplate = null;
    private RedisTemplate<String, Object> redisTemplate = null;

    // 注入StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    // 测试字符串
    @GetMapping("/string/{key}/{value}")
    public Map<String, String> string(@PathVariable("key") String key,@PathVariable("value") String value) {
        stringRedisTemplate.opsForValue().set(key, value);
        Map<String, String> result = new HashMap<>();
        result.put(key, value);
        return result;
    }

    // 测试对象
    @GetMapping("/object/{id}")
    public User object(@PathVariable("id") Long id){
        User user = new User();
        user.setId(id);
        user.setUserName("user_name" + id);
        user.setNote("note_" + id);
        user.setSex(SexEnum.getSexEnum(id.intValue() % 2));
        redisTemplate.opsForValue().set("user_" + id, user);
        return user;
    }

    @Autowired
    private UserService userService = null;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Long id) {
        return userService.getUser(id);
    }
}

然后根据控制器中的地址请求即可完成测试即可

Redis缓存管理器

首先在Spring Boot的配置文件中添加缓存配置


  data:
    redis:
      host: 127.0.0.1
      port: 6379
    jedis:
      pool:
        # 最大连接数
        max-active: 20
        # 最大等待时间
        max-wait: 2000ms
        # 最大空闲连接数
        max-idle: 10
        # 最小空闲等待连接数
        min-idle: 5
    #    # 哨兵模式配置
    #    sentinel:
    #      # 主服务器
    #      master: 192.168.80.130:26379
    #      # 节点
    #      nodes: 192.168.80.131:26379, 192.168.80.132:26379
    #    # 集群配置
    #    cluster:
    #      # 集群节点
    #      nodes: 192.168.80.133:7001, 192.168.80.133:7002, 192.168.80.133:7003, 192.168.80.133:7004, 192.168.80.133:7005, 192.168.80.133:7006
    #      # 单一连接最大转向次数
    #      max-redirects: 10
    # 缓存管理器配置
    cache:
      # 缓存管理器名称
      cache-names: redisCache
      # 缓存管理器类型,可不配,Spring Boot会自动发现
      # type: redis
      redis:
        # 十分钟超时时间,配置为0 则永不超时
        time-to-live: 600000ms
        # 缓存空值
        cache-null-values: true
        # 缓存key前缀
        key-prefix: 'sbdev::'
        # key是否使用前缀
        use-key-prefix: true

然后通过缓存注解的方式来使用Redis

package com.sbdev.service.impl;

import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao  = null;

    /**
     * 从Redis缓存中获取用户信息。
     * 使用了@Cacheable注解,使得该方法的结果可以被缓存。如果缓存中已存在对应键的用户信息,
     * 则直接从缓存中返回,避免了再次查询数据库。缓存的键由'redis_user_'和用户ID拼接而成,
     * 保证了键的唯一性。
     * @param id 用户的ID,作为查询和缓存键的一部分。
     * @return 用户对象,包含用户信息。
     */
    @Override
    @Cacheable(value = "redisCache", key = "'redis_user_'+#id")
    public User getUser(Long id) {
        // 通过UserDao查询Redis中的用户信息。
        return userDao.getUser(id);
    }

    @Override
    // 事务管理器由Spring Boot自动装配,无需自己配置
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }

}

然后通过控制器对应的方法进行测试

    @Autowired
    private UserService userService = null;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Long id) {
        return userService.getUser(id);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1858747.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

思考-生涯思考-GPT-5对人们的影响

GPT-5 一年半后发布&#xff1f;对此你有何期待&#xff1f; IT之家6月22日消息&#xff0c;在美国达特茅斯工程学院周四公布的采访中&#xff0c;OpenAI首席技术官米拉穆拉蒂被问及GPT-5是否会在明年发布&#xff0c;给出了肯定答案并表示将在一年半后发布。此外&#xff0c;…

Java知识点整理 11— 后端 Spring Boot 万用初始化模板使用

一. 模块简介 annotation&#xff1a;自定义注解aop&#xff1a;请求日志和权限校验common&#xff1a;通用类config&#xff1a;配置类constant&#xff1a;常量 controller&#xff1a;控制层esdao&#xff1a;方便操作ESexception&#xff1a;异常类job&#xff1a;定时任务…

企业邮箱老板如何看员工邮件往来记录

员工离职&#xff0c;删除邮箱内重要邮件&#xff1f;已删除的邮件能否恢复&#xff1f;企业邮箱老板如何查看员工邮件的往来记录呢&#xff1f;本篇文章将为您详细介绍企业邮件自动备份的原理以及邮箱内的设置方法。 一、为何查看员工邮件往来记录&#xff1f; 企业邮箱关乎…

了解SD-WAN与传统WAN的区别

近年来&#xff0c;许多企业选择了SD-WAN作为他们的网络解决方案。云基础架构的SD-WAN不仅具备成本效益&#xff0c;而且提供更安全、更可靠的WAN连接&#xff0c;有助于实现持续盈利。客户能够更好地控制他们的网络&#xff0c;个性化定制且无需额外成本。 那么&#xff0c;为…

第二十八篇——复盘:世界不完美,我们该怎么办?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 对于信息传递过程中的相关知识的总结&#xff0c;让我又仿佛回到了每一个…

猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests

猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests &#x1f42f; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎&#xff0c;一名专注于人工智能领域的博主。在AI开发中&#xff0c;我们经常会遇到各种各样的错误&#xff0c;其中API Rate Limits…

多路h265监控录放开发-(15)回放页面中的三个槽函数进行视频的录放(0.1版本项目完结篇)

xviewer.h 中的回放页面的三个槽函数&#xff1a; void SelectCamera(QModelIndex index);//选择摄像机129void SelectDate(QDate date); //选择日期129void PlayVideo(QModelIndex index); //选择时间播放视频129 SelectCamera槽函数解析&#xff1a; 点击相机列表日…

【路由交换技术】Cisco Packet Tracer基础入门教程(四)

Hello各位&#xff0c;好久不见&#xff0c;第四期我准备讲一下Packet Tracer中DHCP的配置&#xff0c;使用方法。 本章实验我们将拓扑中的某个路由器作为DHCP服务器&#xff08;它仍然可作为路由器使用&#xff09;&#xff0c;通过命令配置DHCP服务。独立的服务器可通过图形化…

【AI大模型】GPTS 与 Assistants API

前言 2023 年 11 月 6 日&#xff0c;OpenAI DevDay 发表了一系列新能力&#xff0c;其中包括&#xff1a;GPT Store 和 Assistants API。 GPTs 和 Assistants API 本质是降低开发门槛 可操控性和易用性之间的权衡与折中&#xff1a; 更多技术路线选择&#xff1a;原生 API、…

创新指南|品牌电商新策略:五大转型思路与RGM举措

在流量红利过去的背景下&#xff0c;品牌电商面对多渠道运营的难题&#xff0c;如缺乏统盘经营、绩效管理分散、价格战失控、用户体验不足以及流量过度依赖&#xff0c;品牌电商如何有效应对这些挑战&#xff0c;本文从5个维度探讨全渠道电商RGM破局之路&#xff0c;实现品牌的…

利用chrome_remote_interface实现程序化、自动化Web安全测试

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…

MOE学习笔记

MOE网络结构 和传统的 transformer 网络结构相比&#xff0c;我们将 Transformer 模型的每个 FFN 层替换为 MoE 层&#xff0c;MoE 层由门网络&#xff08;Router&#xff09;和一定数量的专家&#xff08;Expert&#xff09;组成。 这些 Expert 其实也是 FFN 层&#xff0c;…

LeetCode 算法:二叉树的直径 c++

原题链接&#x1f517;&#xff1a;二叉树的直径 难度&#xff1a;简单⭐️ 题目 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由…

审美进阶:7个小程序模板,助你提高设计感!

小程序是一种无需下载和安装即可使用的应用程序。小程序实现了应用程序“触手可及”的梦想。用户可以通过扫描或搜索打开应用程序。对于开发者来说&#xff0c;小程序也大大降低了开发成本。因此&#xff0c;越来越多的品牌争相制作小程序应用程序。本文将为您带来优秀的微信小…

ESP32-S3方案应用设备无线交互技术,产品远程控制与语音交互

在物联网和人工智能(AI)技术融合的浪潮中&#xff0c;ESP32-S3芯片以其卓越的性能和多功能性&#xff0c;成为智能家居和工业自动化领域的明星产品。 ESP32-S3是一款基于Xtensa LX6处理器的嵌入式系统级芯片&#xff0c;具有高效、低功耗的特点。集成的Wi-Fi和蓝牙功能&#x…

结合人工智能的在线教育系统:开发与实践

人工智能&#xff08;AI&#xff09;正在革新各行各业&#xff0c;教育领域也不例外。结合AI技术的在线教育系统能够提供个性化的学习体验、智能化的教学辅助和高效的数据分析&#xff0c;从而大大提升教育质量和学习效果。本文将探讨结合AI技术的在线教育系统的开发与实践&…

【数据结构】比较顺序表和链表的区别(优缺点),细讲CPU高速缓存命中率

目录 一、顺序表和链表的区别【表格】 二、顺序表优缺点 三、链表优缺点 四、缓存命中率&#xff08;缓存利用率&#xff09; ❥ 主存和本地二级存储 ❥ 寄存器和三级缓存 ❥ 顺序表缓存命中率 ❥ 链表缓存命中率 一、顺序表和链表的区别【表格】 不同点顺序表链表&am…

【十二】图解 Spring 核心数据结构:BeanDefinition

图解 Spring 核心数据结构&#xff1a;BeanDefinition 简介 使用spring框架的技术人员都知道spring两个大核心技术IOC和AOP&#xff0c;随着投入更多的时间去学习spring生态&#xff0c;越发觉得spring的发展不可思议&#xff0c;一直都是引领着Java EE的技术变革&#xff0c;这…

MySQL之可扩展性(一)

可扩展性 概述 有些应用仅仅适用于一台或少数几台服务器&#xff0c;那么哪些可扩展性建议是和这些应用相关的呢&#xff1f;大多数人从不会维护超大规模的系统&#xff0c;并且通常也无法效仿在主流大公司所使用的策略。选择一个合适的策略能够大大地节约时间和金钱。 MySQL…

ONLYOFFICE 桌面编辑器8.1---一个高效且强大的办公软件

软件介绍 ONLYOFFICE 桌面编辑器经过不断的更新换代现在迎来了&#xff0c;功能更加强大的ONLYOFFICE 桌面编辑器8.1是一个功能强大的办公套件&#xff0c;专为多平台设计&#xff0c;包括Windows、Linux和macOS。它提供了一套全面的办公工具&#xff0c;包括文档处理、电子表…