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

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:- SBDevspring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: Ms123!@#server:servlet:context-path: /SBDevport: 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 配置后的数据源实例。*/@BeanDataSource 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自动创建不需要我们干预@Autowiredprivate 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: - SBDevspring:# 数据源配置,用于连接和管理数据库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 如果设置参数时发生错误*/@Overridepublic 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 如果获取结果时发生错误*/@Overridepublic 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 如果获取结果时发生错误*/@Overridepublic 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 如果获取结果时发生错误*/@Overridepublic 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: SBDevspring:# 数据源配置,用于连接和管理数据库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.pojologging: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 {@Autowiredprivate 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 {// 用户服务接口@Autowiredprivate 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 查询操作中可能抛出的任何异常。*/@Overridepublic 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 包装了当前插件的目标对象的代理实例。*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}/*** 设置插件的属性。* 该方法用于接收从配置文件中读取的插件属性,当前插件未使用任何属性,因此该方法为空实现。** @param properties 插件的属性配置。*/@Overridepublic 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: SBDevspring:# 数据源配置,用于连接和管理数据库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.xmllogging: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 {@Autowiredprivate 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 如果在预处理阶段发生异常*/@Overridepublic 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 如果在后处理阶段发生异常*/@Overridepublic 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 如果在完成处理阶段发生异常*/@Overridepublic 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: SBDevspring:# 数据源配置,用于连接和管理数据库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.xmllogging: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 用户对象*/@Overridepublic 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.1port: 6379jedis: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@Autowiredprivate 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;}@Autowiredprivate 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.1port: 6379jedis: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: redisredis:# 十分钟超时时间,配置为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 {@Autowiredprivate 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);}}

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

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

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

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

相关文章

微积分-导数2(导数函数)

在前面的部分中&#xff0c;我们考虑了函数 f f f在固定点 a a a处的导数&#xff1a; f ′ ( a ) lim ⁡ h → 0 f ( a h ) − f ( a ) h \begin{equation}f(a) \lim_{h \to 0} \frac{f(ah) - f(a)}{h}\end{equation} f′(a)h→0lim​hf(ah)−f(a)​​​ 如果我们将等式中…

Redis持久化(RDB、AOF)详解

Redis持久化详解 一、Redis为什么需要持久化&#xff1f; Redis 是一个基于内存的数据库&#xff0c;拥有极高的读写性能&#xff0c;但是内存中的数据在断电或服务器重启时会全部丢失&#xff0c;因此需要一种持久化机制来将数据保存到硬盘上&#xff0c;以便在需要时进行恢复…

华为数通——STP-RSTP-MSTP生成树

STP 为了提高网络可靠性&#xff0c;交换机之间常常会进行设备冗余&#xff08;备份&#xff09;&#xff0c;但这样会给交换网络带来环路风险&#xff0c;导致广播风暴以及MAC地址表不稳定等问题。 STP&#xff1a;生成树协议的作用就是为了解决避免二层环路&#xff0c;解决…

STM32 DAC模块的应用(FW_F1_V1.8.5)

目录 概述 1 STM32Cube配置项目 1.1 软件版本信息 1.2 配置DAC模块参数 1.3 GENERATE Project 2 DAC库函数介绍 2.1 初始化函数&#xff1a;HAL_DAC_Init 2.2 启动DAC数据转换&#xff1a;HAL_DAC_Start 2.3 停止DAC数据转换&#xff1a;HAL_DAC_Stop 2.4 设置通道数…

CentOS停止维护,如何应对?

一、事件背景 2020年12月08日&#xff0c;CentOS官方宣布了停止维护CentOS Linux的计划&#xff0c;并推出了CentOS Stream项目。 更多信息&#xff0c;请参见CentOS官方公告。 版本变化说明CentOS 9不再支持新的软件和补丁更新CentOS 82021年12月31日停止维护服务CentOS 720…

【征服数据结构】:期末通关秘籍

【征服数据结构】&#xff1a;期末通关秘籍 &#x1f498; 数据结构的基本概念&#x1f608; 数据结构的基本概念&#x1f608; 逻辑结构和存储结构的区别和联系&#x1f608; 算法及其特性&#x1f608; 简答题 &#x1f498; 线性表&#xff08;链表、单链表&#xff09;&…

HTML5【新特性总结】

HTML5【新特性总结】 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。…

小牛G0 60拆机

日常通勤的GOVA G0 60 的后刹车线断了&#xff0c;需要自已换刹车线&#xff0c;翻阅网上的资料后&#xff0c;可能该条刹车线需要全部拆解&#xff0c;因此开贴记录 应该不用全拆&#xff0c;但是如上图&#xff0c;后刹车线有2条绑带&#xff0c;因此更换要拆到这个位置。 1…

Hi3861 OpenHarmony嵌入式应用入门--LiteOS Event

CMSIS 2.0接口使用事件标志是实时操作系统&#xff08;RTOS&#xff09;中一种重要的同步机制。事件标志是一种轻量级的同步原语&#xff0c;用于任务间或中断服务程序&#xff08;ISR&#xff09;之间的通信。 每个事件标志对象可以包含多个标志位&#xff0c;通常最多为31个&…

CSS justify-content 不生效的原因 失效

MDN文档&#xff1a; https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content CSS justify-content 属性定义浏览器如何沿着弹性容器的主轴和网格容器的行向轴分配内容元素之间和周围的空间。 justify-content什么情况下会不生效&#xff08;失效&#xff09;&a…

《看不影子的少年》一部探讨偏见与接纳的电视剧❗

《看不见影子的少年》这部电视剧以其独特的视角和深刻的主题 给我留下了深刻的印象。该剧讲述了一位与众不同的少年 他无法在阳光下留下影子&#xff0c;象征着他在社会中的孤独与不被理解 观看过程中&#xff0c;可以感受到少年内心的挣扎与渴望 他渴望被接纳&#xff0c;渴…

APM教程-SkyWalking安装和配置

SkyWalking简介 APM (Application Performance Management) 即应用性能管理&#xff0c;属于IT运维管理&#xff08;ITOM)范畴。主要是针对企业 关键业务的IT应用性能和用户体验的监测、优化&#xff0c;提高企业IT应用的可靠性和质量&#xff0c;保证用户得到良好的服务&#…

Java如何设置Map过期时间的的几种方法

一、技术背景 在实际的项目开发中&#xff0c;我们经常会使用到缓存中间件&#xff08;如redis、MemCache等&#xff09;来帮助我们提高系统的可用性和健壮性。 但是很多时候如果项目比较简单&#xff0c;就没有必要为了使用缓存而专门引入Redis等等中间件来加重系统的复杂性…

oracle开放某些视图给特定用户,查询报视图不存在问题

以sysdba身份登录到Oracle数据库。 创建新用户。例如&#xff0c;创建一个名为new_user的用户&#xff0c;密码为password&#xff1a; CREATE USER new_user IDENTIFIED BY password;为新用户分配表空间和临时表空间。例如&#xff0c;将表空间users和临时表空间temp分配给新…

数据库精选题(七)(综合模拟题二)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;数据库 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 一、名词解释 1、事务 2、弱实体集 3、正…

chatglm系列知识

一、目录 chatglm 是什么语言模型与transformer decoder 的区别解释prefix LM与Cause LMchatglm&#xff08;prefix LM&#xff09;与decoder-only LM 核心区别glm 架构chatglm 预训练方式chatglm 微调chatglm与chatglm2、chatglm3的区别chatglm 激活函数采用gelu, 为什么chat…

06 - matlab m_map地学绘图工具基础函数 - 绘制海岸线

06 - matlab m_map地学绘图工具基础函数 - 绘制海岸线 0. 引言1. 关于m_coast2. 关于m_gshhs3. 关于m_gshhs_c、m_gshhs_I、m_gshhs_i、m_gshhs_h、m_gshhs_f4. 关于m_shaperead5. 结语 0. 引言 本篇介绍下m_map中添加绘制海岸线的一系列函数及其用法&#xff0c;主要函数包括m…

【HTML03】HTML表单语法笔记,附带案例-作业

文章目录 表单概述一、表单容器&#xff08;form&#xff09;二、控件相关单词获取本次课程作业和案例 表单概述 允许用户输入信息&#xff0c;和提交信息的-收集用户信息。 表单&#xff1a;表单容器表单控件组成。 控件&#xff1a;输入框、单选按钮、多选、下拉框、多行文…

分布式数据库系统MyCat

MyCat简介 MyCat是一个开源的分布式数据库系统&#xff0c;是一个实现了MySQL协议的服务器&#xff0c;前端用户可以把它看作是一个数据库代理&#xff0c;用MySQL客户端工具和命令行访问&#xff0c;而其后端可以用MySQL原生协议与多个MySQL服务器通信&#xff0c;也可以用JD…

FreeRTOS实时操作系统

1.认识实施操作系统 1.1 裸机和实时操作系统 裸机&#xff1a; 早期嵌入式开发没有嵌入式操作系统的概念&#xff0c;直接操作裸机&#xff0c;在裸机上写程序&#xff0c;比如用51单片机基本就没有操作系统的概念。 通常把程序设计为前后台系统&#xff0c;主要分为两部分&a…